{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Overview\n",
    "\n",
    "In this project, I will build an item-based collaborative filtering system using [MovieLens Datasets](https://grouplens.org/datasets/movielens/latest/). Specically, I will train a KNN models to cluster similar movies based on user's ratings and make movie recommendation based on similarity score of previous rated movies. \n",
    "\n",
    "\n",
    "## [Recommender system](https://en.wikipedia.org/wiki/Recommender_system)\n",
    "A recommendation system is basically an information filtering system that seeks to predict the \"rating\" or \"preference\" a user would give to an item. It is widely used in different internet / online business such as Amazon, Netflix, Spotify, or social media like Facebook and Youtube. By using recommender systems, those companies are able to provide better or more suited products/services/contents that are personalized to a user based on his/her historical consumer behaviors\n",
    "\n",
    "Recommender systems typically produce a list of recommendations through collaborative filtering or through content-based filtering\n",
    "\n",
    "This project will focus on collaborative filtering and use item-based collaborative filtering systems make movie recommendation\n",
    "\n",
    "\n",
    "## [Item-based Collaborative Filtering](https://beckernick.github.io/music_recommender/)\n",
    "Collaborative filtering based systems use the actions of users to recommend other items. In general, they can either be user based or item based. User based collaborating filtering uses the patterns of users similar to me to recommend a product (users like me also looked at these other items). Item based collaborative filtering uses the patterns of users who browsed the same item as me to recommend me a product (users who looked at my item also looked at these other items). Item-based approach is usually prefered than user-based approach. User-based approach is often harder to scale because of the dynamic nature of users, whereas items usually don't change much, so item-based approach often can be computed offline.\n",
    "\n",
    "\n",
    "## Data Sets\n",
    "I use [MovieLens Datasets](https://grouplens.org/datasets/movielens/latest/).\n",
    "This dataset (ml-latest.zip) describes 5-star rating and free-text tagging activity from [MovieLens](http://movielens.org), a movie recommendation service. It contains 27753444 ratings and 1108997 tag applications across 58098 movies. These data were created by 283228 users between January 09, 1995 and September 26, 2018. This dataset was generated on September 26, 2018.\n",
    "\n",
    "Users were selected at random for inclusion. All selected users had rated at least 1 movies. No demographic information is included. Each user is represented by an id, and no other information is provided.\n",
    "\n",
    "The data are contained in the files `genome-scores.csv`, `genome-tags.csv`, `links.csv`, `movies.csv`, `ratings.csv` and `tags.csv`.\n",
    "\n",
    "## Project Content\n",
    "1. Load data\n",
    "2. Exploratory data analysis\n",
    "3. Train KNN model for item-based collaborative filtering\n",
    "4. Use this trained model to make movie recommendations to myself\n",
    "5. Deep dive into the bottleneck of item-based collaborative filtering.\n",
    " - cold start problem\n",
    " - data sparsity problem\n",
    " - popular bias (how to recommend products from the tail of product distribution)\n",
    " - scalability bottleneck\n",
    "6. Further study"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/opt/conda/lib/python3.6/site-packages/fuzzywuzzy/fuzz.py:11: UserWarning: Using slow pure-python SequenceMatcher. Install python-Levenshtein to remove this warning\n",
      "  warnings.warn('Using slow pure-python SequenceMatcher. Install python-Levenshtein to remove this warning')\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "import time\n",
    "\n",
    "# data science imports\n",
    "import math\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "from scipy.sparse import csr_matrix\n",
    "from sklearn.neighbors import NearestNeighbors\n",
    "\n",
    "# utils import\n",
    "from fuzzywuzzy import fuzz\n",
    "\n",
    "# visualization imports\n",
    "import seaborn as sns\n",
    "import matplotlib.pyplot as plt\n",
    "plt.style.use('ggplot')\n",
    "\n",
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "# path config\n",
    "data_path = os.path.join(os.environ['DATA_PATH'], 'MovieLens')\n",
    "movies_filename = 'movies.csv'\n",
    "ratings_filename = 'ratings.csv'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. Load Data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "df_movies = pd.read_csv(\n",
    "    os.path.join(data_path, movies_filename),\n",
    "    usecols=['movieId', 'title'],\n",
    "    dtype={'movieId': 'int32', 'title': 'str'})\n",
    "\n",
    "df_ratings = pd.read_csv(\n",
    "    os.path.join(data_path, ratings_filename),\n",
    "    usecols=['userId', 'movieId', 'rating'],\n",
    "    dtype={'userId': 'int32', 'movieId': 'int32', 'rating': 'float32'})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<class 'pandas.core.frame.DataFrame'>\n",
      "RangeIndex: 58098 entries, 0 to 58097\n",
      "Data columns (total 2 columns):\n",
      "movieId    58098 non-null int32\n",
      "title      58098 non-null object\n",
      "dtypes: int32(1), object(1)\n",
      "memory usage: 680.9+ KB\n"
     ]
    }
   ],
   "source": [
    "df_movies.info()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<class 'pandas.core.frame.DataFrame'>\n",
      "RangeIndex: 27753444 entries, 0 to 27753443\n",
      "Data columns (total 3 columns):\n",
      "userId     int32\n",
      "movieId    int32\n",
      "rating     float32\n",
      "dtypes: float32(1), int32(2)\n",
      "memory usage: 317.6 MB\n"
     ]
    }
   ],
   "source": [
    "df_ratings.info()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>movieId</th>\n",
       "      <th>title</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>1</td>\n",
       "      <td>Toy Story (1995)</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>2</td>\n",
       "      <td>Jumanji (1995)</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>3</td>\n",
       "      <td>Grumpier Old Men (1995)</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>4</td>\n",
       "      <td>Waiting to Exhale (1995)</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>5</td>\n",
       "      <td>Father of the Bride Part II (1995)</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   movieId                               title\n",
       "0        1                    Toy Story (1995)\n",
       "1        2                      Jumanji (1995)\n",
       "2        3             Grumpier Old Men (1995)\n",
       "3        4            Waiting to Exhale (1995)\n",
       "4        5  Father of the Bride Part II (1995)"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df_movies.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>userId</th>\n",
       "      <th>movieId</th>\n",
       "      <th>rating</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>1</td>\n",
       "      <td>307</td>\n",
       "      <td>3.5</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>1</td>\n",
       "      <td>481</td>\n",
       "      <td>3.5</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>1</td>\n",
       "      <td>1091</td>\n",
       "      <td>1.5</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>1</td>\n",
       "      <td>1257</td>\n",
       "      <td>4.5</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>1</td>\n",
       "      <td>1449</td>\n",
       "      <td>4.5</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   userId  movieId  rating\n",
       "0       1      307     3.5\n",
       "1       1      481     3.5\n",
       "2       1     1091     1.5\n",
       "3       1     1257     4.5\n",
       "4       1     1449     4.5"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df_ratings.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "There are 283228 unique users and 53889 unique movies in this data set\n"
     ]
    }
   ],
   "source": [
    "num_users = len(df_ratings.userId.unique())\n",
    "num_items = len(df_ratings.movieId.unique())\n",
    "print('There are {} unique users and {} unique movies in this data set'.format(num_users, num_items))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. Exploratory data analysis\n",
    " - Plot the counts of each rating\n",
    " - Plot rating frequency of each movie"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 1. Plot the counts of each rating"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "we first need to get the counts of each rating from ratings data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>count</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>rating</th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0.5</th>\n",
       "      <td>442388</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1.0</th>\n",
       "      <td>886233</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1.5</th>\n",
       "      <td>441354</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2.0</th>\n",
       "      <td>1850627</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2.5</th>\n",
       "      <td>1373419</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3.0</th>\n",
       "      <td>5515668</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3.5</th>\n",
       "      <td>3404360</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4.0</th>\n",
       "      <td>7394710</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4.5</th>\n",
       "      <td>2373550</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5.0</th>\n",
       "      <td>4071135</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "          count\n",
       "rating         \n",
       "0.5      442388\n",
       "1.0      886233\n",
       "1.5      441354\n",
       "2.0     1850627\n",
       "2.5     1373419\n",
       "3.0     5515668\n",
       "3.5     3404360\n",
       "4.0     7394710\n",
       "4.5     2373550\n",
       "5.0     4071135"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# get count\n",
    "df_ratings_cnt_tmp = pd.DataFrame(df_ratings.groupby('rating').size(), columns=['count'])\n",
    "df_ratings_cnt_tmp"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can see that above table does not include counts of zero rating score. So we need to add that in rating count dataframe as well"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>count</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0.0</th>\n",
       "      <td>15235120248</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>0.5</th>\n",
       "      <td>442388</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1.0</th>\n",
       "      <td>886233</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1.5</th>\n",
       "      <td>441354</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2.0</th>\n",
       "      <td>1850627</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2.5</th>\n",
       "      <td>1373419</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3.0</th>\n",
       "      <td>5515668</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3.5</th>\n",
       "      <td>3404360</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4.0</th>\n",
       "      <td>7394710</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4.5</th>\n",
       "      <td>2373550</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5.0</th>\n",
       "      <td>4071135</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "           count\n",
       "0.0  15235120248\n",
       "0.5       442388\n",
       "1.0       886233\n",
       "1.5       441354\n",
       "2.0      1850627\n",
       "2.5      1373419\n",
       "3.0      5515668\n",
       "3.5      3404360\n",
       "4.0      7394710\n",
       "4.5      2373550\n",
       "5.0      4071135"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# there are a lot more counts in rating of zero\n",
    "total_cnt = num_users * num_items\n",
    "rating_zero_cnt = total_cnt - df_ratings.shape[0]\n",
    "# append counts of zero rating to df_ratings_cnt\n",
    "df_ratings_cnt = df_ratings_cnt_tmp.append(\n",
    "    pd.DataFrame({'count': rating_zero_cnt}, index=[0.0]),\n",
    "    verify_integrity=True,\n",
    ").sort_index()\n",
    "df_ratings_cnt"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The count for zero rating score is too big to compare with others. So let's take log transform for count values and then we can plot them to compare"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>count</th>\n",
       "      <th>log_count</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0.0</th>\n",
       "      <td>15235120248</td>\n",
       "      <td>23.446869</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>0.5</th>\n",
       "      <td>442388</td>\n",
       "      <td>12.999943</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1.0</th>\n",
       "      <td>886233</td>\n",
       "      <td>13.694735</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1.5</th>\n",
       "      <td>441354</td>\n",
       "      <td>12.997603</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2.0</th>\n",
       "      <td>1850627</td>\n",
       "      <td>14.431035</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2.5</th>\n",
       "      <td>1373419</td>\n",
       "      <td>14.132814</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3.0</th>\n",
       "      <td>5515668</td>\n",
       "      <td>15.523103</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3.5</th>\n",
       "      <td>3404360</td>\n",
       "      <td>15.040568</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4.0</th>\n",
       "      <td>7394710</td>\n",
       "      <td>15.816275</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4.5</th>\n",
       "      <td>2373550</td>\n",
       "      <td>14.679897</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5.0</th>\n",
       "      <td>4071135</td>\n",
       "      <td>15.219432</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "           count  log_count\n",
       "0.0  15235120248  23.446869\n",
       "0.5       442388  12.999943\n",
       "1.0       886233  13.694735\n",
       "1.5       441354  12.997603\n",
       "2.0      1850627  14.431035\n",
       "2.5      1373419  14.132814\n",
       "3.0      5515668  15.523103\n",
       "3.5      3404360  15.040568\n",
       "4.0      7394710  15.816275\n",
       "4.5      2373550  14.679897\n",
       "5.0      4071135  15.219432"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# add log count\n",
    "df_ratings_cnt['log_count'] = np.log(df_ratings_cnt['count'])\n",
    "df_ratings_cnt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Text(0, 0.5, 'number of ratings')"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 864x576 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "ax = df_ratings_cnt[['count']].reset_index().rename(columns={'index': 'rating score'}).plot(\n",
    "    x='rating score',\n",
    "    y='count',\n",
    "    kind='bar',\n",
    "    figsize=(12, 8),\n",
    "    title='Count for Each Rating Score (in Log Scale)',\n",
    "    logy=True,\n",
    "    fontsize=12,\n",
    ")\n",
    "ax.set_xlabel(\"movie rating score\")\n",
    "ax.set_ylabel(\"number of ratings\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "It's interesting that there are more people giving rating score of 3 and 4 than other scores "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 2. Plot rating frequency of all movies"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>userId</th>\n",
       "      <th>movieId</th>\n",
       "      <th>rating</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>1</td>\n",
       "      <td>307</td>\n",
       "      <td>3.5</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>1</td>\n",
       "      <td>481</td>\n",
       "      <td>3.5</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>1</td>\n",
       "      <td>1091</td>\n",
       "      <td>1.5</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>1</td>\n",
       "      <td>1257</td>\n",
       "      <td>4.5</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>1</td>\n",
       "      <td>1449</td>\n",
       "      <td>4.5</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   userId  movieId  rating\n",
       "0       1      307     3.5\n",
       "1       1      481     3.5\n",
       "2       1     1091     1.5\n",
       "3       1     1257     4.5\n",
       "4       1     1449     4.5"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df_ratings.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>count</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>movieId</th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>68469</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>27143</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>15585</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>2989</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>15474</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "         count\n",
       "movieId       \n",
       "1        68469\n",
       "2        27143\n",
       "3        15585\n",
       "4         2989\n",
       "5        15474"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# get rating frequency\n",
    "df_movies_cnt = pd.DataFrame(df_ratings.groupby('movieId').size(), columns=['count'])\n",
    "df_movies_cnt.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Text(0, 0.5, 'number of ratings')"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAvQAAAH3CAYAAADUoMslAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDMuMC4wLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvqOYd8AAAIABJREFUeJzs3Xuc3FV9//HX2UsukAQMi0C4yVUlCihYVFpBTa2tl6LgAQUVrWChYtWf9YctICJWq1brFQWxgor1qCgKlUp/VdCiVEARohRFuUYCgRBIQi678/398f1umJ2dzU52Z3fmZF/Px2MemfleZj6zJ5D3nP18z4SiKJAkSZKUp55OFyBJkiRp4gz0kiRJUsYM9JIkSVLGDPSSJElSxgz0kiRJUsYM9JIkSVLGDPSSVCeEcGIIYbDTdah9QghvCyHcG0KohRDOmORz3RNCOL3u8Y9DCJ+dfJXtFUJ4UwhhXafrkDQ9DPSSshJC+GIIoahuQ1XAujiEsOsWPs9u1XMc2bDra8AWPddEVR8eiia3rguIuQoh7A58FHgf5bh+bJzjD6/+Xv2kTa/fVzeuf9Fk/3enaMy/AuzZ5ueU1KUM9JJy9CNgF2AP4DXAM4Cvt+OJi6J4rCiK5e14rhYNUb6X+tu7mh0YSv3TWNvWYB8gAJcVRfGHoijWjHP8m4FPAweEEJ7exjruAt5Uv6H6sPFnwN1tfB2gI3+PJXWQgV5SjjYURXFfURT3FkVxDXA+8JwQwoLhA0IIrwkhXBdCWBVCWBFCuCKEsH/dcwyHqB9UM6R3VOeNaLkZflzN3N4YQlgbQrghhPCs+oJCCC8MIdwcQlgXQvhlCOGI6nlPGO/NVO+l/vZI9Zxvqp5vSQjhF8AG4Mhq35+FEH4SQnisaie5MISwsK6enhDCP4YQHgghPBpC+GoI4Z31bRghhHNDCLc2vI8jq7p3q9v2rBDCVSGE1SGE+0MI36jC6IjnCSG8MoTwv9VxPwgh7NPw3M8KIfxHCOGRqqbrQgiHhhD2q9ph/qjh+BdUP/vdGEMI4WXVuKwPISwPIXwqhLDNcF3AD6pDlzW+rybP9QTgGOA8IAEnj3XsBFwIvDSEsHPdtr+q6ruroY5ZIYQPhRCWhRA2hBBuCSEcW7f/ayGEf29S/1UhhC9W90e13LQwjruHEC6t/nt5LIRwewjhHW1595KmlIFeUtZCCIsoQ9hQdRs2GzgXeCbwp9W+K0IIs6r9z6z+PJpyVnxEQG/QA3wA+NvqvPuBFELoq2rYFfgucF21/+2M09qxBfqBfwTeBjwF+HkI4UXAt4AvA08HXgHsB3yj7ry3A28F3gEcAtwEbHH/eDVL/UPK34ocCiyhnPG+qu5nCbAbcBLwauCPge2Bz9c9z4HA1cAK4AWUP6ePA71FUfyGMtie1PDyJwHfK4rinjFqewbwbeC/gIOANwBHUc6wA3wQiNX9AynHedlm3u7rgJuLovg18EXghBDC3M0cvyVuA64FTqxq7wXeCFzQ5Nh/qva9FXgaZRvYV8Pj7WEXAS8KIew0fEL1QeUFwMXNXrzFcfwcMK/a91TKn//mfl6SukVRFN68efOWzY0yaA0Cq4G1QFHdPjLOeQur4w6vHu9WPT6y4bgTgcGGxwXwzLpth1Xbnlw9fj9wB2U4HT7mxdUxJ2ympuHnXt1w27va/6Zq/3MazvsxcG7Dtr2rY59WPb4PeG/DMd8G1tU9Phe4teGYI6vn2a16/GXgyw3HzAXWAy+te56NwA51xxxP+SGqv3r8VeBGIIzxs4jAo8C86vEOwDrgZZv5+X0VuLZh29FADdi1erykej87t/B36xbglLrHvwFObDjmHuD0hrH47Gaes696/eMo28N+SxmkXwosp/zAtuk5gPmUv4k5ueF5vgt8v7rfW43v2+v2nw7cOfzzrf7u1I91K+O4FDijE/9de/PmbXI3Z+gl5eg64GDgjygvdvwJDbPPIYSDQwjfCiH8PoTwKI+3NUzkQsGCcoZ72PCs5fAM6QHAz4qiqP8NQasXVQ5Rvpf6W31PdQ24vuGcQ4F3Vq0Tq0MIq4FfVvv2q1pvdqKcEa734xZrqvcs4FUNr/UAZRDdr+64u4uieLDu8TLK32zsWD0+BPjPoiiKMV7nW5Qf0F5dPX4t5Wz+qNaSOouBaxq2XU0ZmA/Y7LtqEEI4HNiX8kPCsIspe+rb5ZuUv7l4PmU7zxeLotjYcMx+lD/bZu9rMUD19+wrlD+jYa+lDOxj/XxbGcePAWeFEH4aQvhgCOGPJ/ImJU2/vk4XIEkT8FhRFL+t7t9S9Wp/kqplo+qh/j5lgH0D5UwolDOQs9hytYawPhyaepps22J176WZjU1CXw/lB5mvNjn+Plp/jzXK8Fuv8aLbHsrfiny4yfkr6u5vaNjX7Gc0pqIoNoYQvkA5hhdQzjB/oeHnPpXeTNmmtSKETT+SAPSEEJ5eFMXNk32BoijWhxAuBs4CDqdsh5qoi4F3hBAWU860H0DZejWWccexKIrPhxC+R/nbpSOB/wghfL0oihMnUaekaWCgl7Q1OBv4dQjhc0VRXE/Z/7sj8A9F2Q9NCOG5jAyvwwG0tw2v/yvgNSGE3roA+uw2PO9YbgAWb+6DQAhhOfBc4D/qNh/ecNj9wE4hhJ6iKGrVtmc2HHM9cOA4HzparXlJCCFsZhb5AuD/hhD+mnIMLxznOZcCz2vYdgTlh4lftVpYdTHsq4C/Bv67YfdnKWfTT2v1+cZxPvBr4Adj/Ex/Q9m+9Dyg/oLlIyhbggAoiuKmEMJNlH3/c4HriqK4bTOv29I4FkVxL+XP/cIQwn8AXwohnFoUxdrx35qkTrHlRlL2ivKiyu9S9rJD2Uu8HjgthLBPCOGFlBdg1gfJFZT96i8KIexchbqJ+gxli8t5IYSnhhCeX1fLhGfuN+NM4OgQwkeq1qJ9Qwh/HkL417oLHP+Zcgb3+GoVmXdRtnrU+y9gAXB29XM6Fjil4Zj3A08P5Vr/zwoh7F2tPvPJEMKWtC/9E+Us8pdCCIdUNR8bQjhs+ICiKH4HXEU5Vt8viuLOcZ7zQ8Bh1c/hKaFc5/3jwEVVMG3V6yg/4H2xKIpb6m/AJbTx4tiiKG6lvD7gpWPsfxT4FPCPIYSjQwj7hxDOBF5CeXF0vYspr1U4jvJC2c0ZdxxDCJ8JIby4+ruwmHLG/w7DvNT9DPSSthYfpgznRxZFsQI4gXJ1m6XAR4B3UraYAFDNSP8N5cWY9wA/n+gLV+Hx5ZQz4r+gDJVnVrvb/m2dRVH8J+XFns+kbCu6iTLAP8zjK/18lHK1l09QvrdDKS9erX+eX1G2mrwWuJky2P59wzG3UM7sb08ZtpdSzjLPAlZtQc2/oGzj2IWyP/znlCv3NH4r7/Bzn9/Cc/6cclWbF1D+DL5IeeHv37RaV+Uk4DtFUaxvsu8blBeqHttk34QURfHQOCH5dOALlG1kt1BdUFsUxdUNx32F8oPkdsC/jfOarYxjT91rXkPZgjTqy7AkdZ8w9m8+JUkTFUJ4HuWFjAe2o/+6HUIIbwI+VRTFnE7XMpYQwluBdwO7F0XRGPYlSU3YQy9JbRBCOIVylngZZWvJxyj7mrsizHe7EMI8ym/+fSfwScO8JLXOlhtJao89Kdse/pfym0Z/RNn3rNZ8lrIN5ybKdiFJUotsuZEkSZIy5gy9JEmSlDEDvSRJkpQxL4rdcvYoSZIkabo0fqP3KAb6CVi2bFlHXndgYIAVK1aMf6C6lmOYP8cwf45h/hzD/DmGrVm0aFFLx9lyI0mSJGXMQC9JkiRlzEAvSZIkZcweekmSJHVcURSsW7eOWq1GCONeB7rVKIqCnp4e5syZM+H3baCXJElSx61bt47+/n76+mZePB0cHGTdunXMnTt3QufbciNJkqSOq9VqMzLMA/T19VGr1SZ+fhtr2awY41uAE4GnA19NKZ1Yt++FwKeBPYDrgBNTSndW+2YD5wHHAGuBD6WUPjrV50qSJGn6zKQ2m2Ym8/6nc4Z+GXAu8IX6jTHGAeBS4ExgIXA98LW6Q84G9gP2BJ4PvCvG+OJpOFeSJElqiwsuuIDHHntsSp572gJ9SunSlNK3gQcbdr0SWJpS+npKaR1lCD8oxviUav/rgfellFamlH4NXEA50z/V50qSJElt8fnPfz7/QL8Zi4Gbhh+klNYAtwOLY4xPAHap31/dXzyV57blXUmSJCkrX//611myZAlLlizhtNNO4+677+ZVr3oVS5YsIcbIvffeC8Db3vY2Lr/88k3n7bfffgBce+21HHPMMZx00kk873nP4y1veQtFUXDhhReyfPlyXvWqV3HMMce0ve5uuPJgHvBAw7ZVwPxq3/Djxn1Tee4IMcaTgZMBUkoMDAyM/W6mUF9fX8deW+3hGObPMcyfY5g/xzB/zcZw+fLlmy6KHbzkc9Tu+l1bX7Nnj73pe82bx9x/66238olPfILLL7+cHXbYgZUrV3Laaadx3HHHceyxx3LJJZdw1llncdFFF9HT00Nvb++Ii3j7+vro7e3llltu4ZprrmHnnXfmpS99KTfeeCNvfvObueCCC7j00kvZYYcdmr7+7NmzJ/z3uhsC/WpgQcO2BcCj1b7hx+sa9k3luSOklM4Hzq8eFitWrNjsG5oqAwMDdOq11R6OYf4cw/w5hvlzDPPXbAzXr19Pb28vUK54UxRFW1+zVqsxODg45v5rrrmGl7zkJWy33XYMDg4yf/58rr/+ei644AIGBwd5xStewTnnnMPg4CC1Wo2hoaERzzc4OMjQ0BAHH3wwT3ziE6nVahxwwAHccccdHHLIIRRFMeqcxvff+DNZtGhRS++tGwL9UspedwBijNsC+1D2t6+MMf4BOAi4qjrkoOqcKTu3re9OkiRJW6TnuJM6XcJm1S8zWavV2Lhx46Z9s2bN2nS/t7d3sx8i2lbPlL9CJcbYV71eL9AbY5wDDALfAj4cYzwauAI4C/hlSunW6tSLgTNijNcDOwEnAW+o9k3luZIkSZohDj/8cP7qr/6Kk08+mYULF7Jy5UoOPfRQLrvsMo455hguvfRSDjvsMAB22203br75Zl7+8pfz/e9/f0SgH8u8efNYvXo1CxcubHvt03lR7BnAY8DpwAnV/TNSSg8ARwPvB1YChwHH1Z33HsqLVe8ErgY+nFK6EmCKz5UkSdIM8eQnP5m3vvWtHHPMMSxZsoT3vve9nHvuuXzta19jyZIlfPOb3+Scc84B4Pjjj+cnP/kJS5Ys4YYbbmCbbbYZ9/mPP/54jj/++Cm5KDa0uz9pBiiWLVvWkRe2ZzB/jmH+HMP8OYb5cwzz12wM165d21Iw3lo1e/9VD/243zjVDctWqgXF2tUU69d3ugxJkiR1GQN9JmpvO4E137yo02VIkiSpyxjoJUmSpIwZ6CVJktRxM/26zsm8fwO9JEmSOq6np2da1mzvRoODg/T0TDyWd8MXS6lVM/yTqyRJ2nrNmTOHdevWsX79ekIYd2GXrUZRFPT09DBnzpwJP4eBPhcz5++1JEmagUIIzJ07t9NlZMmWG0mSJCljBnpJkiQpYwZ6SZIkKWMG+px4UawkSZIaGOhzMYOu9pYkSVLrDPSSJElSxgz0kiRJUsYM9Bmxg16SJEmNDPTZsIdekiRJoxnoJUmSpIwZ6CVJkqSMGeglSZKkjBnoc+IXS0mSJKmBgT4XXhMrSZKkJgz0kiRJUsYM9JIkSVLGDPQ5sYdekiRJDQz02bCJXpIkSaMZ6CVJkqSMGeglSZKkjBnoJUmSpIwZ6LPiRbGSJEkayUCfi+BFsZIkSRrNQC9JkiRlzEAvSZIkZcxAL0mSJGXMQJ8Tr4mVJElSAwN9LrwmVpIkSU0Y6CVJkqSMGeglSZKkjBnoc1LYRC9JkqSRDPTZsIlekiRJoxnoJUmSpIwZ6CVJkqSMGeglSZKkjBnos+JFsZIkSRrJQJ+L4EWxkiRJGs1AnxOXrZQkSVIDA302nKGXJEnSaAZ6SZIkKWMG+pzYciNJkqQGBvpc2HEjSZKkJgz0kiRJUsYM9Llw2UpJkiQ1YaDPiT30kiRJamCgz4Yz9JIkSRrNQC9JkiRlzECfkcKWG0mSJDUw0OfCjhtJkiQ1YaDPijP0kiRJGslAnw2n6CVJkjSagT4nTtBLkiSpgYE+F36xlCRJkpow0OfEVW4kSZLUwECfCyfoJUmS1ISBXpIkScqYgT4nttxIkiSpgYE+G/bcSJIkaTQDfVacoZckSdJIBvpcuGylJEmSmjDQ58QJekmSJDUw0EuSJEkZM9BLkiRJGTPQ58RlKyVJktSgr9MFAMQYnwR8BngOsB74BvC2lNJgjPFg4ELgqcCvgb9KKf2iOi8AHwTeVD3V54HTU0pFtX/C53YdL4qVJElSE90yQ/8Z4H5gF+Bg4Ajg1BjjLOAy4MvAE4CLgMuq7QAnA0cBBwEHAi8D3gwwmXO7V3d+1pAkSVLndEug3wtIKaV1KaX7gCuBxcCRlL9F+JeU0vqU0icov2HpBdV5rwf+OaV0T0rpXuCfgROrfZM5t/s4Qy9JkqQmuqLlBvgX4LgY4w8pZ9P/HDiTMtT/sqEN5pfV9uHQf1PdvpuqbUzy3BFijCdTzuiTUmJgYGDL3+EkPdDTQwihI6+t9unr63MMM+cY5s8xzJ9jmD/HsL26JdBfQxmYHwF6Kdtjvg2cAaxqOHYVML+6P69h/ypgXtUf37iv5XMb++hTSucD51cPixUrVmzRm2uHWq2gqNXoxGurfQYGBhzDzDmG+XMM8+cY5s8xbM2iRYtaOq7jLTcxxh7KGfNLgW2BAcpZ+n8CVgMLGk5ZADxa3W/cvwBYXQXyyZwrSZIkZaHjgR5YCOwBfKrqdX8Q+FfgL4ClwIHVjPuwA6vtVH8eVLfvoIZ9Ez23O7lspSRJkhp0vOUmpbQixvh74JQY40coW2FeT9nv/kNgCHhrjPGzwEnVaf9V/Xkx8I4Y479TLgHzf4BPVvsmc2738ZpYSZIkNdENM/QArwReDDwA/BbYCLw9pbSBcmnJ1wEPA28Ejqq2A3wO+C5wM3ALcEW1jcmc262cn5ckSVKjUNjGsaWKZcuWTfuLDv3dG5hz6HPZeOxJ4x+sruVFQPlzDPPnGObPMcyfY9ia6qLYcfs0umWGXq3ww5ckSZIaGOhz4RdLSZIkqQkDvSRJkpQxA31ObLmRJElSAwN9Luy4kSRJUhMG+qw4Qy9JkqSRDPTZcIpekiRJoxnoc+IEvSRJkhoY6HPhspWSJElqwkAvSZIkZcxAnxOXrZQkSVIDA70kSZKUMQN9VpyhlyRJ0kgG+lx4UawkSZKaMNDnxB56SZIkNTDQ58IZekmSJDVhoJckSZIyZqDPiS03kiRJamCglyRJkjJmoJckSZIyZqDPhRfFSpIkqQkDfU7soZckSVIDA302nKGXJEnSaAb6jBTO0EuSJKmBgT4X9tBLkiSpCQO9JEmSlDEDfVZsuZEkSdJIBvpc2HEjSZKkJgz0OXGCXpIkSQ0M9Nlwil6SJEmjGehz4rKVkiRJamCgz4XLVkqSJKkJA70kSZKUMQN9Tmy5kSRJUgMDvSRJkpQxA31WnKGXJEnSSAb6XHhRrCRJkpow0OfECXpJkiQ1MNDnwhl6SZIkNWGglyRJkjJmoM+Jy1ZKkiSpgYFekiRJypiBPivO0EuSJGkkA30uvChWkiRJTRjoc2IPvSRJkhoY6LPhDL0kSZJGM9BLkiRJGTPQ58SWG0mSJDUw0OfCjhtJkiQ1YaDPiPPzkiRJamSgz4XLVkqSJKkJA31O7KGXJElSAwN9NpyhlyRJ0mgGekmSJCljBvqc2HIjSZKkBgb6XHhRrCRJkpow0GfFGXpJkiSNZKCXJEmSMmagz4kT9JIkSWpgoM+FPfSSJElqwkAvSZIkZcxAnxOXrZQkSVIDA30ubLmRJElSEwb6rDhDL0mSpJEM9JIkSVLGDPQ5sYdekiRJDfpaOSjG+GrgFymlX8cYnwxcAAwBp6SUbp3KAlWxh16SJElNtDpDfy7wUHX/I8D/AFcDn5mKoiRJkiS1pqUZemDHlNLyGOMc4I+BY4CNwIopq0yj2XIjSZKkBq3O0D8QY9wX+HPgZyml9cAcwD6Q6WLLjSRJkppodYb+fcANlH3zx1bblgA3tbOYGONxwHuAPYD7gBNTSj+KMb4Q+HS1/bpq+53VObOB8yh/a7AW+FBK6aN1zznhcyVJkqRu19IMfUrpi8AuwG4ppauqzT8FjmtXITHGPwX+CXgDMB94HvC7GOMAcClwJrAQuB74Wt2pZwP7AXsCzwfeFWN8cfWcEz5XkiRJykGrq9z0AOvq7gOsSCnV2ljLe4FzUko/rR7fW73eycDSlNLXq8dnAytijE+pVth5PeWs+0pgZYzxAuBE4ErglZM4t/vYQy9JkqQGrbbcDNLka0pjjIPAMspZ8PeklFZPpIgYYy9wKPCdGONvKfvzvw38HbCYutaelNKaGOPtwOIY43LK3xzUt/7cBBxV3Z/Mud3FHnpJkiQ10WqgP40y6H4QuJuyH/1dwBXA/1L2vf8L8KYJ1rET0E/Zy/4nlCvoXAacAcwDHmg4fhVlW868useN+5jkuZtUvyU4GSClxMDAQItvq30e6usnQEdeW+3T19fnGGbOMcyfY5g/xzB/jmF7tRro3wE8M6U0HH5vizFeD9yQUtonxngz5UWzE/VY9ecnU0p/AIgxfpQy0F8DLGg4fgHwKLC67vG6hn1U+yd67iYppfOB86uHxYoV079a59DgILP6++nEa6t9BgYGHMPMOYb5cwzz5xjmzzFszaJFi1o6rtVlKxcA2zRs2wbYrrp/HzC3xecapephv4eRbT3D95cCBw1vjDFuC+xD2Ru/EvhD/f7q/tI2nCtJkiR1vVZn6C8Grooxfpyy5WY34G+Bi6r9L6JsvZmMfwVOizFeSdly83bgcuBbwIdjjEdTtvicBfyyuqh1uLYzqt8Y7AScRLlSDpM8t+sUXhQrSZKkBq3O0P8d8CnKZSo/BryGcm33d1X7fwAcMcla3gf8DLgN+DXwc+D9KaUHgKOB9wMrgcMYuVzme4DbgTuBq4EPp5SuBJjMuV3Ha2IlSZLURHDWd4sVy5Ytm/YXHfrQ6fTPnkPtb8+e9tdW+9gzmD/HMH+OYf4cw/w5hq2peujHndZtteWGGOOLgIN5fHUYAFJKZ21pcZoAl62UJElSE61+sdSngEjZWrO2bpfT+9PJ36ZIkiSpQasz9K8BDkop3T2VxWhzgoFekiRJo7R6UewK4OGpLETjCAF/ISJJkqRGrc7Q/zPwlRjjB4Dl9TtSSr9re1UaracHagZ6SZIkjdRqoD+v+vOlDdsLoLd95WizilqnK5AkSVKXaSnQp5Rabc3RVAk9UAx2ugpJkiR1GYN6LoLfFCtJkqTRxpyhjzFemVJ6cXX/R4xxRWZK6XlTVJvqhR5XuZEkSdIom2u5ubju/uenuhCNI7hspSRJkkYbM9CnlC6pe3hrSum6xmNijH80JVVptBC8KFaSJEmjtNpDf9UY269sVyEahzP0kiRJamKzq9zEGHuAAIQYY6juD9sHcNmV6WIPvSRJkpoYb9nKQR6/GLYxvNeA97e9IjUXgJotN5IkSRppvEC/F2WUvBqoX82mAB5IKT02VYWpQehx2UpJkiSNstlAn1K6s7q75zTUos0I9tBLkiSpiZa+KRYgxvhy4AhggLpe+pTS66agLjVylRtJkiQ10dIqNzHG9wCfq45/FfAg8GfAw1NXmkYIYYyv9pIkSdJM1uqylW8E/jSl9HZgQ/Xny4AnTVVhauAMvSRJkppoNdBvn1K6pbq/IcbYn1L6H8oWHE0He+glSZLURKuB/vYY4+Lq/i3AKTHG1wIrp6YsjeI69JIkSWqi1YtizwB2qO6fDlwCzANOnYqi1ESAolYb8c1ekiRJ0riBvvq22HXATwGqVpt9p7guNQo9eFWsJEmSGo3bcpNSqgGXpZQ2TEM9GksIUDPQS5IkaaRWe+iviTE+e0or0ea5yo0kSZKaaLWH/k7gezHGy4C7qev9SCmdNRWFqYHr0EuSJKmJVgP9XODb1f3d6rYbMadL6HGGXpIkSaO0FOhTSm+Y6kI0Dle5kSRJUhOt9tCr04JDJUmSpNFMibkIAWq23EiSJGkkA30uQvCbYiVJkjTKmIE+xvjhuvsvmJ5yNCYDvSRJkprY3Az9yXX3vz3mUZoerkMvSZKkJja3ys1NMcZvAL8CZscYz2l2kOvQTxPXoZckSVITmwv0x1DO0u8JBGD3JscYMadL6KFwhl6SJEkNxgz0KaX7gXMBYox9rkXfYQFbbiRJkjRKy18sFWN8AvAyYFfgXuDylNJDU1mc6oQefx8iSZKkUVpatjLG+BzgduCvgQOBNwO/rbZrOnhRrCRJkppoaYYe+Bfg1JTSvw1viDEeC3wCeNZUFKYGIUDNKXpJkiSN1OoXS+0PpIZt3wD2bW85GlMI2HMjSZKkRq0G+t8AxzVsexVlG46mQ+iBmi03kiRJGqnVlpu3AZfHGN8K3Ak8CdgPeOkU1aVGofyjKApCCJ2tRZIkSV2jpRn6lNK1wD7Ap4AbgE8C+1bbNR1CNVSFbTeSJEl6XKsz9KSUVgJfnsJatDnDk/IGekmSJNVptYdeneYMvSRJkpow0OcibGqi72wdkiRJ6iqtfrGUwb/TNgV6V7qRJEnS48YN6jHGXmBNjHH2NNSjsWwK9J0tQ5IkSd1l3ECfUhoCbgN2mPpyNKZNPfTO0EuSJOlxra5y8xXKdeg/DtxD3TxxSum/pqIwNXCVG0mSJDXRaqA/pfrz7IbtBbB326rR2FzlRpIkSU20FOhTSntNdSEah6vcSJIkqYmWv1gqxtgPPBtYlFL6WoxxW4CU0pqpKk51XOVGkiRJTbS6bOXTKS+MvQC4sNp8BPCFKapLjVzlRpIkSU20ur78ecBZKaWnABurbVcDfzwlVWk0V7mRJElSE60G+sXAl6v7BWxqtZk7FUWpCVe5kSRJUhOtBvo7gEPqN8QY/wj4bbsL0hhc5UaSJElNtHpR7JnAFTHGzwKzYozvBv4aOGnKKtNIztBLkiSpiZY55j+rAAAd8ElEQVRm6FNKlwMvBnak7J3fE3hlSun7U1ib6jlDL0mSpCZaXrYypfRz4NQprEWb4zr0kiRJaqKlQB9jnAWcAbwaWAQsA/4NeH9Kad3UladNXIdekiRJTbQ6Q38e8GTgrcCdlC03fw/sCrxxakrTCM7QS5IkqYlWA/1RwD4ppYerx7+KMV5HucqNgX462EMvSZKkJlpdtvI+YJuGbXOBP7S3HI3JVW4kSZLUxJgz9DHGF9Q9/BJwZYzxk8A9wO7A3wAXT2152sQZekmSJDWxuZabC5ts+/uGx28G/ql95WhM9tBLkiSpiTEDfUppr+ksRONwlRtJkiQ10WoPvTosbAr0na1DkiRJ3aXVdegPAj4GHAzMqzYHoEgpzZqi2lTPGXpJkiQ10eqylV8Fvkm5Dv1jU1eOxhQ2LXPT0TIkSZLUXVoN9DsDZ6WUTJOdMrzKTc0hkCRJ0uNaDfQXAa8BvjKFtRBj3A+4GfhGSumEattrgA8AA8BVwBtTSg9V+xZSrsbzImAF8O6U0iV1zzfhc7uO69BLkiSpiVYD/QeBn8QY/x5YXr8jpfSC5qdMyKeBnw0/iDEuBj4HvAS4ETgf+AxwXN3xG4CdKPv7r4gx3pRSWjqZc9v4ftpneIbelhtJkiTVaTXQfwP4PfAtpqiHPsZ4HPAwcC2wb7X5eOC7KaVrqmPOBH4dY5wP1ICjgaellFYDP44xfgd4LXD6JM/tPsM99LbcSJIkqU6rgf5gYIeU0oapKCLGuAA4B3gB8Ka6XYspAz4AKaXbY4wbgP0pQ/lgSum2uuNvAo5ow7ndx1VuJEmS1ESrgf5HwAHAL6aojvcBF6aU7okx1m+fB6xqOHYVMB8YAh4ZY99kzx0hxngycDJASomBgYFx3k77rd9uex4GtttuO2Z14PXVHn19fR35+6P2cQzz5xjmzzHMn2PYXq0G+t8D348xfovRPfRnTaaAGOPBwBLgGU12rwYWNGxbADxKOcs+1r7JnjtCSul8yh58gGLFihXNDptSxaNlaatWriR04PXVHgMDA3Ti74/axzHMn2OYP8cwf45haxYtWtTSca1+U+w2wBXALGD3httkHQk8Cbgrxngf8E7g6BjjjcBS4KDhA2OMewOzgduqW1+1Ms6wg6pzmOS53cdVbiRJktRESzP0KaU3TGEN5wP/Vvf4nZQB/xTgiZSr6/wJ5Uo15wCXppQeBYgxXgqcE2N8E2Wf/18Cz62e5yuTOLf7DK9yY6CXJElSnZYCfTW73VRK6XeTKSCltBZYW/daq4F1KaUHgAdijH9NGc53AP4TqP9wcSrwBeB+4EHglOFlJ6ulKyd0blcz0EuSJKlOKFoIiDHGGuUC6KFucwGQUuqdmtK6VrFs2bLpf9H/vZnaR/6Bnv9zLuEpB07766s97BnMn2OYP8cwf45h/hzD1lQ99GG841ptuRnRax9j3Bl4D+XqN5oOm5atdIZekiRJj2v1otgRUkr3AW8DPtDecjQ2A70kSZJGm1CgrzyZcvUbTYeeTcvcdLQMSZIkdZdWL4r9ESOT5DaU38R6zlQUpSaGV7mpGeglSZL0uFa/WOrzDY/XADellH7T5no0HltuJEmSVKfVi2IvmupCNI6eajGhWq2zdUiSJKmrtNpyMws4kfILmObV70spva79ZWmU3uFAP9TZOiRJktRVWm25uQg4CPgusHzqytGYhgP90GBn65AkSVJXaTXQvxjYK6X08FQWo82oWm6KoaHxv11AkiRJM0ary1beBcyeykI0jl576CVJkjRaqzP0FwOXxRg/TkPLTUrpv9pelUaz5UaSJElNtBro31L9+Y8N2wtg7/aVozF5UawkSZKaaHXZyr2muhCNY3jZyiFbbiRJkvS4Vnvo1Wm23EiSJKkJA30ubLmRJElSEwb6XGxquTHQS5Ik6XEG+lz0Vpc7GOglSZJUx0Cfi55qqGy5kSRJUh0DfSZCCGXbjTP0kiRJqmOgz0mfgV6SJEkjGegzEnr6DPSSJEkawUCfk95ee+glSZI0goE+JwZ6SZIkNTDQZyT02nIjSZKkkQz0OfGiWEmSJDUw0Gck9PTC0GCny5AkSVIXMdDnpLcParVOVyFJkqQuYqDPSW8vhS03kiRJqmOgz0jo67PlRpIkSSMY6DMS+mfBxo2dLkOSJEldxECfk75+GDTQS5Ik6XEG+oyEWbNg44ZOlyFJkqQuYqDPSf8sZ+glSZI0goE+I6F/Fgx6UawkSZIeZ6DPSOjvt+VGkiRJIxjoc2LLjSRJkhoY6DPispWSJElqZKDPSOh32UpJkiSNZKDPiS03kiRJamCgz0jonwVDQxS1oU6XIkmSpC5hoM9I6O8v72x06UpJkiSVDPQZCf2zyjuDLl0pSZKkkoE+J8OB3pVuJEmSVDHQZ2RTy40XxkqSJKlioM/JLGfoJUmSNJKBPiOhb7iH3kAvSZKkkoE+I2HTDL0XxUqSJKlkoM9JvzP0kiRJGslAn5HQ50WxkiRJGslAn5HgRbGSJElqYKDPiS03kiRJamCgz8hwy03hDL0kSZIqBvqMuMqNJEmSGhnoc7Kp5Waws3VIkiSpaxjoMxL6naGXJEnSSAb6jIR+l62UJEnSSAb6nLgOvSRJkhoY6DMSenqgr8916CVJkrSJgT43ff3O0EuSJGkTA31u+md5UawkSZI2MdDnxhl6SZIk1THQ56avDza6Dr0kSZJKBvrc9M+iGLTlRpIkSSUDfW5mzYb16zpdhSRJkrqEgT43c7eBdY91ugpJkiR1CQN9bmbNdpUbSZIkbWKgz0yYNRvWr+90GZIkSeoSBvrczJoFGwz0kiRJKhnoczNrjhfFSpIkaRMDfW5mz4ENBnpJkiSVDPS5mT0bBgcpBv1yKUmSJEFfpwsAiDHOBj4DLAEWArcD704pfa/a/0Lg08AewHXAiSmlO+vOPQ84BlgLfCil9NG6557wuV1p1pzyzw3roG9eZ2uRJElSx3XLDH0fcDdwBLAdcAaQYoxPijEOAJcCZ1KG/euBr9WdezawH7An8HzgXTHGFwNM5tyuNacK9K50I0mSJLpkhj6ltIYyXA+7PMb4e+AQYAdgaUrp6wAxxrOBFTHGp6SUbgVeTznrvhJYGWO8ADgRuBJ45STO7U7DM/ReGCtJkiS6JNA3ijHuBOwPLAVOAW4a3pdSWhNjvB1YHGNcDuxSv7+6f1R1f/Ekzq2v52Tg5Oo5GBgYmPR7nIi+vj4W7Lgjq4Dt586hv0N1aOL6+vo69vdH7eEY5s8xzJ9jmD/HsL26LtDHGPuBrwAXpZRujTHOAx5oOGwVMB+YV/e4cR/V/omeu0lK6Xzg/OphsWLFipbfTzsNDAzw6GABwMPL7iFst0NH6tDEDQwM0Km/P2oPxzB/jmH+HMP8OYatWbRoUUvHdUsPPQAxxh7gS8AG4C3V5tXAgoZDFwCPVvto2D+8b7Lndqdtty3/XLums3VIkiSpK3RNoI8xBuBCYCfg6JTSxmrXUuCguuO2Bfah7I1fCfyhfn91f2kbzu1Oc8tfLBRrV49zoCRJkmaCbmq5OQ94KrAkpfRY3fZvAR+OMR4NXAGcBfyyuqgV4GLgjBjj9ZQfBk4C3tCGc7vTphl6A70kSZK6ZIY+xrgn8GbgYOC+GOPq6nZ8SukB4Gjg/cBK4DDguLrT30O5bv2dwNXAh1NKVwJM5tyuNXsu9PTAGgO9JEmSIBRF0ekaclMsW7asIy88fAHJ0NuPJxxyOD0nnNqROjRxXgSUP8cwf45h/hzD/DmGrakuig3jHdcVM/TaQtvM86JYSZIkAQb6PG07n2JNdy/GI0mSpOlhoM/Rgu3h0VXjHydJkqStnoE+Q2HefFjtDL0kSZIM9HmatwBWP4IXNEuSJMlAn6P528PGDfCYF8ZKkiTNdAb6DIUddyrvPLC8s4VIkiSp4wz0OdpuYfnnIys7W4ckSZI6zkCfo23nAVD4bbGSJEkznoE+R9suKP9c/Uhn65AkSVLHGehzNG8+9PbBKltuJEmSZjoDfYZCCLD9Qnj4wU6XIkmSpA4z0Odq4QDFg/d3ugpJkiR1mIE+U2FgZ5etlCRJkoE+WwM7waqHKDZu6HQlkiRJ6iADfa523BmKAmy7kSRJmtEM9JkKA9W3xa6w7UaSJGkmM9Dnascy0Bf20UuSJM1oBvpcLXgC9PU7Qy9JkjTDGegzFXp64Im7UNx3T6dLkSRJUgcZ6HO20yK463edrkKSJEkdZKDPWJi/PTz8IMXQUKdLkSRJUocY6HO2x97lnw/8obN1SJIkqWMM9BkLe+5T3ll2V2cLkSRJUscY6HP2xF0AKB64r8OFSJIkqVMM9BkL28yDeQtg+bJOlyJJkqQOMdDnbtc9Ke65o9NVSJIkqUMM9JkLu+8F995BUXOlG0mSpJnIQJ+73feCDRtguSvdSJIkzUQG+syF3culK4u7/YIpSZKkmchAn7tddoPePr8xVpIkaYYy0Gcu9PXDHntT3H5rp0uRJElSBxjotwJhvwPgjtsoNm7odCmSJEmaZgb6rUDY9wAYHIQ7b+90KZIkSZpmBvqtwX4HQE8Pxc03dLoSSZIkTTMD/VYgzFsATzmI4qc/6HQpkiRJmmYG+q1E2H8xPPQAxdrVnS5FkiRJ08hAv5UI+z61vPPbX3e2EEmSJE0rA/3WYq/9obeP4ralna5EkiRJ08hAv5UIs2bDk/aluPWXnS5FkiRJ08hAvxUJBx8Gd/6WYsXyTpciSZKkaWKg34qEZz4XgOLGn3S4EkmSJE0XA/1WJDxxF9h9L4obr+10KZIkSZomBvqtTHjmc+H2WylWPtjpUiRJkjQNDPRbmXDI4QAUP7ftRpIkaSYw0G9lwi67wS67U9xg240kSdJMYKDfCoVDDoff/IrikZWdLkWSJElTzEC/FQqHPBeKGsX/XNPpUiRJkjTFDPRbobDbk2C/Ayiu+g7F4GCny5EkSdIUMtBvpXr+7Gh46AGKn/6g06VIkiRpChnot1YHHgp77ktxRaIY3NjpaiRJkjRFDPRbqRACPUcdDyuWU/zg3ztdjiRJkqaIgX4rFp52COy/mOKqyyjWru50OZIkSZoCBvqtXM8rXgerHqL45kWdLkWSJElTwEC/lQv7PpXw/JdQ/Ogqil/9vNPlSJIkqc0M9DNAOOp42HlXap//KMXqRzpdjiRJktrIQD8DhDnb0HPyO2HtamoXfZKiVut0SZIkSWoTA/0MEXbbi/DK18EvrqP4ziWdLkeSJElt0tfpAjR9wp8eBcvuLtem32NvwjOf2+mSJEmSNEnO0M8gIQTCCafAnvtS+8LHKW76WadLkiRJ0iQZ6GeY0NdPz6l/X14k++n3U7vmyk6XJEmSpEkw0M9AYeEAPX/3j7D4GRRf+gy1Cz9GscYvnpIkScqRgX6GCrPn0PM3/0B42aspfnYNtbNPo7jlxk6XJUmSpC1koJ/BQl8fPS9/NT3v/jDM3Ybax88ul7Vcu6bTpUmSJKlFBnoR9tyXnjM/RvizV1Bc+/+onX0ate9/i2Ld2k6XJkmSpHG4bKUACP2zCMe8geLgZ1O79CKKr/8rxZWXEv7oeYRD/xj2fjKhx89/kiRJ3cZArxHCvk+l910fpLj91nKW/uorKf7fd+EJA4RDDiccengZ7kPodKmSJEnCQK8xhH2eQu8p76Z4bC3FTf9Dcf2PKX54BcV/XgYLBwgHPovw1INg/6cR5i3odLmSJEkzloFemxXmbkN49pHw7CMp1q4pw/2N11L85AcUP/xeedAuuxP2fjLsuS9h511hj30I287raN2SJEkzhYFeLQvbbEt4zvPhOc+nGNwIv/8NxW23UPzufyl+cR38939SDB+8cEfYdU/CLrvBoj0Ji/aAHXeCbefbriNJktRGMz7QxxgXAhcCLwJWAO9OKV3S2aq6X+jrh/0OIOx3AABFUcCqh+Deuyjuuh3uuYNi2V0Ut/4SNm54POjPngM77gJP2IGw/ULYfofH7y/YHuZvB/O3J/T3d+y9SZIk5WTGB3rg08AGYCfgYOCKGONNKaWlnS0rLyGEMpxvvwNh8TM2bS9qQ3D/ffCHuylWLIcH76d44D54+CGKO38LjzxcHtf4hHPmwrbzq9s8mLstYZttN91n7jYwZy5hztzy2Nlzyw8Ls+fA7Nkwaw7Mmu3KPJIkaas3owN9jHFb4GjgaSml1cCPY4zfAV4LnN7R4rYSoacXdt4Vdt6VZo02xeBGWLUSHn4IHn2Y4pGH4dFH4NFVsGY1xZpHYe3q8gPA2jWw5lEY3Pj4+eMV0D9rRMBn1qxy26zZ0NcP/f3lbxv6+qG3F/r6oLfufl8f9PaVj3t6y/t9Ix+H3p7qfrWtp6fuz5H3B9esonjkEQjVvhCqP3ugp+7+iO3V4xCA8Pj96mYLkyRJM9uMDvTA/sBgSum2um03AUd0qJ4ZJ/T1ww5PLG/QNPQ3KjZuhHVr4bG1sH5d+eeGdbB+HcW6dbBh/abHrF9fPl5fbi82bqj2r4e1a8p2oMGN5YeEoSEYGoTB4dvG8YuhhQ8VdR7cgmO3yKawT/WBoO5PQt224Q8CPXU/7FD3HHXPt+lxGHns8PZm2+qfp9m2Uedv5thR2+rrmKRJfAh6sK+PocHB4SfqeD1Tol31dNv7AgiBh0aM4cSfp6u0rZ52jX17nmas9/VQXz9DLf4/uu7JJl8PbL1jP83va+WsWQxt2DD2AW2tZ/LP1RPfSNh1zzbUMjVmeqCfBzzSsG0VML9+Q4zxZOBkgJQSAwMD01Ndg76+vo699kxUFAXUhmBwiKIK+kVtCAY3UgwOlh8AakMUQ0PVh4HquOHttRrUao8fU6vRG2Bo48ZyXzG8v6AYvl+Uj6kNPf76w89TFlVu33SrlZ8oNt0vyg8YtRpQlM9F/fHV+XXPt+kjSUGTbdV5w1uKuu3DW8c5r+4HWr52s/PGfL7hbU2eb4Im+yw9IdDTxnra9jyTfmftfZr2vS9o33srnyeEwKzJ1NfW99YGXfZ3sZiGn08Igf4taanssp9RV/732pb3tgXPsWE9/WO9Zjv/DrXpueYvmE9/F2ewmR7oVwONi6gvAB6t35BSOh84v3pYrFixYhpKG21gYIBOvbbq9UJf74T+6xkYGGCVY5i1J/jfYfb8f2n+HMP85TaGqwA6UO+iRYtaOm6mXzF4G9AXY9yvbttBgBfESpIkKQszOtCnlNYAlwLnxBi3jTEeDvwl8KXOViZJkiS1ZkYH+sqpwFzgfuCrwCkuWSlJkqRczPQeelJKDwFHdboOSZIkaSKcoZckSZIyZqCXJEmSMmaglyRJkjJmoJckSZIyZqCXJEmSMmaglyRJkjJmoJckSZIyZqCXJEmSMmaglyRJkjJmoJckSZIyZqCXJEmSMmaglyRJkjJmoJckSZIyZqCXJEmSMhaKouh0DbnxByZJkqTpEsY7wBn6LRc6dYsx3tDJ1/fmGHpzDLeGm2OY/80xzP/mGG7RbVwGekmSJCljBnpJkiQpYwb6vJzf6QI0aY5h/hzD/DmG+XMM8+cYtpEXxUqSJEkZc4ZekiRJypiBXpIkScpYX6cL0PhijAuBC4EXASuAd6eULulsVTNLjPEtwInA04GvppROrNv3QuDTwB7AdcCJKaU7q32zgfOAY4C1wIdSSh9tx7naMtXP8zPAEmAhcDvlf0vfq/Y7jhmIMX4ZeCGwLXAf5c/z89U+xzATMcb9gJuBb6SUTqi2vQb4ADAAXAW8MaX0ULVvs/8OTuZcbbkY4w+BZwOD1aZ7U0pPrvY5jh3gDH0ePg1sAHYCjgfOizEu7mxJM84y4FzgC/UbY4wDwKXAmZQh8Xrga3WHnA3sB+wJPB94V4zxxZM9VxPSB9wNHAFsB5wBpBjjkxzHrHwAeFJKaQHwcuDcGOMhjmF2Pg38bPhB9W/a54DXUv5bt5byA3j98U3/HZzMuZqUt6SU5lW34TDvOHaIM/RdLsa4LXA08LSU0mrgxzHG71D+hT+9o8XNICmlSwFijIcCu9XteiWwNKX09Wr/2cCKGONTUkq3Aq+nnOlbCayMMV5AOdN/5STP1RZKKa2hDGbDLo8x/h44BNgBxzELKaWldQ+L6rYP5Tg6hhmIMR4HPAxcC+xbbT4e+G5K6ZrqmDOBX8cY5wM1Nv/v4GTOVXs5jh3iDH332x8YTCndVrftJsBPpd1hMeV4AJtC4+3A4hjjE4Bd6vczcuwmc64mKca4E+V/X0txHLMSY/xMjHEtcCvwB+DfcQyzEGNcAJwDvKNhV+MY3E45G7s/4/87OJlzNXEfiDGuiDH+d4zxyGqb49ghBvruNw94pGHbKmB+B2rRaPMox6Pe8PjMq3vcuG+y52oSYoz9wFeAi6oZWMcxIymlUyl/hn9C2SqzHscwF+8DLkwp3dOwfbwx2Ny/g5M5VxPzf4G9gV0p15P/boxxHxzHjrHlpvutBhY0bFsAPNqBWjTa5sZndd3jdQ37JnuuJijG2AN8iXLm5y3VZscxMymlIcpfu58AnIJj2PVijAdTXpT+jCa7NzcGtc3sm+y5moCU0nV1Dy+KMb4a+Ascx45xhr773Qb0VSsCDDuIsk1AnbeUcjyATdc87EPZj7uSsh3goLrj68duMudqAmKMgXKVhJ2Ao1NKG6tdjmO++qh+3jiG3e5I4EnAXTHG+4B3AkfHGG9k9BjsDcym/DdwvH8HJ3Ou2qMAAo5jx/hNsRmIMf4b5X8sbwIOpuwXfW7DxWGaQjHGPsrg8B7Ki2JPolyu6wnAb4E3AlcA7wWOSCk9uzrvg8BzgKMoQ+QPgDeklK6MMe440XOn4z1vjWKMn6X8b2hJdWHV8PYJj4XjOH1ijE8EXgBcDjxGOdt7KfBq4Cc4hl0txrgNI2dZ30kZ8E8Bnkg5hi8BbqRc7aQvpXRcde6Y/w5WK51M6NypfL9bqxjj9sBhwNWU/w4eS9l28wygH8exI5yhz8OpwFzgfv5/e3cTakUZx3H8eymDomgRQu8GgYsiqEWLokX0sqiwN/Pfm1G4KIsSIaIXpNqYRLYQEVoEFpTKL0goSqKNUES2qBaC1kbFjGs3UqLsVtZt8YxxCG/e6zVtrt8PDMyZM88zc84czvzOn2fmwFrgYT/AR90SWoB4CpjfzS9JMkK78n4psIf2JXfXQLvnaBfX7aB9+b10IARMpa0mr6pmAQ/RTgTDVfVTN93rceyNMVr4+4b2Xi8HFid5x2P4/5dkX5LhAxNtiMVokpHunLaQdm3Ld7Sx0Y8MNB/3PDiVtjosM2i3cR6h3Q/+MeDWJF97HI8dK/SSJElSj1mhlyRJknrMQC9JkiT1mIFekiRJ6jEDvSRJktRjBnpJkiSpxwz0kiRJUo8Z6CVJR0VVbaiq+/+Dfh+oqo+PdL+S1BcnHusdkCQdH5LccDjtquoCYBswI8n+I7pTkjQNWKGXJEmSeswKvSQd56pqO7AKuA+4EFgHPAO8BlwFbALmJdnTrX8zsAw4B/iS9hfsW6rqSeDyJHcM9L0CGEqyqKo2Am8kebV7bgHwBHAm8BnwYJIdE9jfM4DVwNXAVuCDqb0DktRvVuglSQBzgeuB2cAcYAMt1M+knSsWAVTVbGAtsLh77n3g3ao6ifZD4MaqOq1b9wSggDX/3FhV3dL1f3vXz0ddvxOxChgFzgIWdJMkHbcM9JIkgJVJdifZRQvXm5J8kWQUWA9c1q13J/Bekg+T/A4sB04Gruyq658Dt3XrXgPsS/LpQba3EFiWZEs3Lv4F4NKqmvVvO9n9SJgLPJvk5ySbgden8sIlqe8M9JIkgN0D878c5PGp3fzZwN/DYpL8CeykDb+BVo2/u5u/h4NU5zuzgBVVtbeq9gI/AEMD/YxnJm246M6BZYccpiNJ05lj6CVJk/EtcMmBB1U1BJwH7OoWvQW8XFXn0ir1V4zTz05gaZI3J7n9EWB/t82t3bLzJ9mHJE0rVuglSZMR4KaquraqZgCPA78CnwAkGQE20i5a3ZZkyzj9vAI8XVUXA1TV6VU175AbT/4A3gaer6pTquoi4Ijf216S+sRAL0masCRfAfOBlcD3tAto5yT5bWC1NcB1jD/chiTrgReBdVX1I7AZmOh96h+lDQEapt2JZ/XkXoUkTS9DY2Njx3ofJEmSJB0mK/SSJElSjxnoJUmSpB4z0EuSJEk9ZqCXJEmSesxAL0mSJPWYgV6SJEnqMQO9JEmS1GMGekmSJKnHDPSSJElSj/0FFG1UZhfKxpYAAAAASUVORK5CYII=\n",
      "text/plain": [
       "<Figure size 864x576 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# plot rating frequency of all movies\n",
    "ax = df_movies_cnt \\\n",
    "    .sort_values('count', ascending=False) \\\n",
    "    .reset_index(drop=True) \\\n",
    "    .plot(\n",
    "        figsize=(12, 8),\n",
    "        title='Rating Frequency of All Movies',\n",
    "        fontsize=12\n",
    "    )\n",
    "ax.set_xlabel(\"movie Id\")\n",
    "ax.set_ylabel(\"number of ratings\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The distribution of ratings among movies often satisfies a property in real-world settings,\n",
    "which is referred to as the long-tail property. According to this property, only a small\n",
    "fraction of the items are rated frequently. Such items are referred to as popular items. The\n",
    "vast majority of items are rated rarely. This results in a highly skewed distribution of the\n",
    "underlying ratings."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's plot the same distribution but with log scale"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Text(0, 0.5, 'number of ratings (log scale)')"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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Ik1ZhmSEdjGSmQbuB47MNVFluZjkYsb7SFpjpEKkfeqvN7thPnqT3LRmSeALkraNXUII9WdTqQghhNvzziDSFtReIiIiIiJSYrqQUkRERESkxFR0i4iIiIiUWLX2dKtnRkRERETaSv07If9MtRbdTJ48OZfj9u7dm6lTpza/oZQtncPKp3NY+XQOK5/OYeXTOcymT58+mbZTe4mIiIiISImp6BYRERERKTEV3SIiIiIiJVa1Pd0iIiIiUlwhBGbNmkUcx5g1e+1g1QghEEURnTt3bvXXraJbRERERDKZNWsWHTp0oLa2/ZWQc+fOZdasWXTp0qVVn6/2EhERERHJJI7jdllwA9TW1hLHcas/X0W3iIiIiGTSnlpKGjI/X7+KbhERERERYOzYsXz33Xcl2beKbhERERER4NJLL1XRLSIiIiJy4403MnDgQAYOHMhBBx3EpEmTGDJkCAMHDsQ5x8cffwzAoYceyp133vnD5/Xr1w+AJ554gl133ZXhw4ez+eabc+CBBxJCYNy4cUyZMoUhQ4aw6667Fj13++yEFxEREZH5El8/ljDpvaLu05Zajmi34Y2uf+ONNzj//PO544476NWrF9OnT+fQQw9lyJAhOOe4/vrrGTlyJJdddlmTx3n55Zd54IEHWHzxxdlpp5149tln2WeffRgzZgw33ngjvXr1KurXBRVQdDvnlgWeBV5JFw3x3n+eXyIRERERycPjjz/ODjvs8ENRvNBCC/H8889z6aWXAjB48GBGjRrV7H4GDBhAnz59AFh99dWZNGkSG2ywQemCUwFFd+ph733xx/lFREREpFWaGpEuB4VT/MVxzPfff//Duo4dO/7wvKamhrlz55Y8T6X0dG/inHvUOXeac659z1UjIiIi0k5tsskm3HnnnUybNg2A6dOns95663H77bcDcMstt7DhhhsC0LdvX1566SUA7rvvvp8U3Y3p3r07M2bMKEn2Nhvpds4dCOwF9AfGe+/3KljXCxgHbANMBY723l+Xrv4EWBGYCYwFfgvc3Fa5RURERKQ8rLzyyhx88MHsuuuuRFHEGmuswahRozjssMO4+OKL6dWrF+eeey4AQ4cOZe+992bgwIFstdVWdO3atdn9Dx06lKFDh7LYYotx0003FTW7hRCKusPGOOd+C8TAtkCXekX3eJJR932AAcBdwMbe+1fq7eM3wEbe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\n",
      "text/plain": [
       "<Figure size 864x576 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# plot rating frequency of all movies in log scale\n",
    "ax = df_movies_cnt \\\n",
    "    .sort_values('count', ascending=False) \\\n",
    "    .reset_index(drop=True) \\\n",
    "    .plot(\n",
    "        figsize=(12, 8),\n",
    "        title='Rating Frequency of All Movies (in Log Scale)',\n",
    "        fontsize=12,\n",
    "        logy=True\n",
    "    )\n",
    "ax.set_xlabel(\"movie Id\")\n",
    "ax.set_ylabel(\"number of ratings (log scale)\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can see that roughly 10,000 out of 53,889 movies are rated more than 100 times. More interestingly, roughly 20,000 out of 53,889 movies are rated less than only 10 times. Let's look closer by displaying top quantiles of rating counts"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1.00    97999.0\n",
       "0.95     1855.0\n",
       "0.90      531.0\n",
       "0.85      205.0\n",
       "0.80       91.0\n",
       "0.75       48.0\n",
       "0.70       28.0\n",
       "0.65       18.0\n",
       "Name: count, dtype: float64"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df_movies_cnt['count'].quantile(np.arange(1, 0.6, -0.05))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So about 1% of movies have roughly 97,999 or more ratings, 5% have 1,855 or more, and 20% have 100 or more. Since we have so many movies, we'll limit it to the top 25%. This is arbitrary threshold for popularity, but it gives us about 13,500 different movies. We still have pretty good amount of movies for modeling. There are two reasons why we want to filter to roughly 13,500 movies in our dataset.\n",
    " - Memory issue: we don't want to run into the “MemoryError” during model training\n",
    " - Improve KNN performance: lesser known movies have ratings from fewer viewers, making the pattern more noisy. Droping out less known movies can improve recommendation quality"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "shape of original ratings data:  (27753444, 3)\n",
      "shape of ratings data after dropping unpopular movies:  (27430748, 3)\n"
     ]
    }
   ],
   "source": [
    "# filter data\n",
    "popularity_thres = 50\n",
    "popular_movies = list(set(df_movies_cnt.query('count >= @popularity_thres').index))\n",
    "df_ratings_drop_movies = df_ratings[df_ratings.movieId.isin(popular_movies)]\n",
    "print('shape of original ratings data: ', df_ratings.shape)\n",
    "print('shape of ratings data after dropping unpopular movies: ', df_ratings_drop_movies.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "After dropping 75% of movies in our dataset, we still have a very large dataset. So next we can filter users to further reduce the size of data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>count</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>userId</th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>16</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>15</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>11</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>736</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>72</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "        count\n",
       "userId       \n",
       "1          16\n",
       "2          15\n",
       "3          11\n",
       "4         736\n",
       "5          72"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# get number of ratings given by every user\n",
    "df_users_cnt = pd.DataFrame(df_ratings_drop_movies.groupby('userId').size(), columns=['count'])\n",
    "df_users_cnt.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Text(0, 0.5, 'number of ratings')"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAuUAAAH3CAYAAAAczCHzAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDMuMC4wLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvqOYd8AAAIABJREFUeJzs3XmcXFWB9//Pqe7OnrC1ggFlFzQsGQFRGRcwo46jjwtwRHEBR1AcddDHmUdnQBhFnZEZd8UBQWFcDwyIwiMP+lNx3FBQEaICgiIQtkAI2TpJp8/vj3s7qa70Ukm661alPu/Xq15d995z7j1Vpzv53lPn3go5ZyRJkiRVp1Z1AyRJkqRuZyiXJEmSKmYolyRJkipmKJckSZIqZiiXJEmSKmYolyRJkipmKJfUFUIIJ4UQBqtuhyZPCOH0EMK9IYShEMIZ27ive0II76lb/lEI4XPb3kpJao6hXFJbCCF8MYSQy8eGMiRdEkLYfQv3s0e5j+c1bPo6sEX72lrlCUAe5WHImyQhhCcCHwU+QNGvH5ug/FHl79VPJ+n4+5V9+oxRtp0TQvj9ZBxHUvforboBklTnf4AI9AD7Ap8BLgWeta07zjmvAdZs6362wAZgj4Z1q0crGEIIQG/Oef2Ut2r7sS8QgCtzzvc1Uf7NFL9PbwghHJxzvnlKWzfFQgjTcs7rqm6HpMnjSLmkdrIu53x/zvnenPMPgfOBZ4YQ5g0XCCG8JoRwfQhheQhhaQjh6hDCk+v2cXf58/vlSOafynojpq8ML5cjqL8MIawOIdwYQjiivkEhhOeHEG4OIQyEEH4TQnhuud/XTvRiytdS/3is3Oebyv0tCiH8GlgHPK/c9sIQwk9DCGvKqRkXhhB2rmtPLYTwoRDCQyGEFSGEr4YQ3h1CGKgrs9lIbQjheWW796hbd0QI4TshhJUhhAdDCJeVI9Aj9hNCeGUI4day3PdDCPs27PuIEML/CyE8Vrbp+hDC4SGE/cupJU9vKH9M+d43nrTUl3lp2S9rQwgPhBA+HUKYNdwu4Ptl0SWNr2uUfe0EHAecByTg1LHKToUQwsHl+/xoCGFVCOF3IYTX1G2fG0L4VAhhSd3v4cvqtg+Pyr86hHBNCGE1cFYIYVoI4ePl78naEMJ9IYQvt/K1SZo8hnJJbSmEMJ8iSG0oH8OmA+cATwP+qtx2dQhhWrn9aeXPY4EnACNCdoMa8GHg78t6DwIphNBbtmF34FvA9eX2dzLBNIkt0Ad8CDgdOBD4VQjhBcAVwJeAg4FXAPsDl9XVeyfwDuBdwGHATcAWz6cOIRwM/IDi04nDgUUUI8/fqXsvoRjtPwV4NfCXwI7A5+v2cwhwHbAUOIbiffoE0JNzvp0iPJ/ScPhTgG/nnO8Zo21/AXwD+B5wKHAy8HKKkW6Af6X4RAXgEIp+XjLOy309cHPO+XfAF4HXhhBmjlN+sn0duB94JkW//m/gUdj4KcnVwALgeOAg4ALg0hDCcxv28xHg4rLsBRS/O68EXkPxe/Iy4OdT/FokTZWcsw8fPnxU/qAIS4PASoppHrl8/PsE9XYuyx1VLu9RLj+vodxJwGDDcgaeVrfuyHLdAeXyB4E/UQTM4TIvKsu8dpw2De97ZcNjn3L7m8rtz2yo9yPgnIZ1+5RlDyqX7wf+paHMN4CBuuVzgN83lHleuZ89yuUvAV9qKDMTWAu8pG4/64Fd6sqcSHEi1FcufxX4JRDGeC8isAKYUy7vAgwALx3n/fsq8JOGdccCQ8Du5fKi8vXs1sTv1i3AaXXLtwMnNZS5B3hPQ198bpx97lce/xmjbBvx/gOrxvp9KV/HGmBuw/pLgMsajvXehjKfAb4z1nvvw4ePzno4Ui6pnVwPLASeTnEB309pGAUOISwMIVwRQvhjCGEF8Ody055bcbxMMdI8bHi0ddfy51OBX+Sc60fqm71QcAPFa6l/3F23fQi4oaHO4cC7y2kiK0MIK4HflNv2L6ex7Ar8pKHej5psU70jgOMbjvUQxQj+/nXl7s45P1y3vITiE4bHlcuHAd/NOecxjnMFxUnWq8vl11GMqv/fcdq2APhhw7rrKEbynzruq2oQQjiKItR+tW71JRRzzFvl34EvlFN/zgohLKzbdgTFpz/3NfTFCYzsB9h8FPwi4C+A20MI55XTjKYhqSN5oaekdrIm5/yH8vkt5dzlT1FOfyjnFF9LEUJPBh4oyy4GtiaMDDUE7uFgWRtl3Rarey2jWZ83v7CzRnEy8tVRyt9P869xiCLA1usb5VhfBM4dpf7SuueNFxOO9h6NKee8PoRwEUUfXkDxKcFFDe/7VHozRehdWswUAYr3pha27YLP5eXPHUbZtiPFpwEA5JzPCiFcQvEpyzHAP4cQPpRzPpvifXyYYmpLo8b3flX9Qs75xhDC3hTTuI6m+Fv5lxDCM3POK7f8JUmqkiPlktrZ2cDJIYTDy+WnUIzQ/nPO+Qe5mCO8EyMD6HCQ6ZmE4/8WOCKEUL+vzW6BN4luBBbknP8wymNlzvkRihORxrvRHNWw/CCwawih/t/4pzWUuQE4ZIxjPbqFbV4U6hLvKC4ADg8hvIWiDy+cYJ+Lgec0rHsuxQnBb5ttWHmB5/HAWxj5icWhwI/Zhgs+c84PAY9QfKrT6OnA7xvK35Fz/kzO+Vjg/cBp5aYbgH6Ku+809sOfmUDOeUXO+fKc89sppl8dBDx7a1+XpOoYyiW1rVxcKPgtirndAHdRzHl+ewhh3xDC8ykuKqwfzV5KMX/7BSGE3cpgtrU+SzFd5LwQwlNCCEfXtWWrR9DHcSZwbAjh38tpOvuFEP46hPCFumkJ/wG8K4RwYnl3k3+kGCWt9z1gHnB2+T69ik0hcNgHgYNDcS/4I0II+5R3RflUCGFLpgL9G8WUkv8KIRxWtvlVIYQjhwvknO+kmPv8CeDanPNdE+zzI8CR5ftwYAjhxWXdi3PO925B215PcZL2xZzzLfUP4Cts+wWf5wL/EIq76ewfQjg0hPBxihOgTwKEEHYo39OjQwh7hRCeBryQTScX11JccPuNEMLLQgh7l+/jO0IIbxzv4CGE/xOKuxE9NYSwD/BGiusybt+G1ySpIoZySe3uXIqA/byc81LgtRQf1y+mmKv7borpGgDknIeAv6O4wPAe4Fdbe+AyAP4vipHpX1MEwzPLzQNj1duG432X4sK/p1FM0bmJIoQ/yqY70HyU4gK/T1K8tsMpLiys389vKaZtvA64mSKc/lNDmVsoRth3pAjMiyluQTmNTVMzmmnzrykuIn0CxTzwX1HcFaTx21OH931+E/v8FcXdVo6heA++SHEx6981267SKcA3c85rR9l2GTAXeNUW7rPeR4B/KNt1I0XAXgA8J+c8PO9/PcVI+BeAW4FvU/xevhagnIv/N8A3KX6/bqW4G8tfA3dOcPwVFL//11O8Ty8BXjHBtClJbSqMfW2OJKlRCOE5FBcdHrIN85EnVQjhTcCnc84zqm7LWEII7wDeCzwx59wY2CWp63mhpySNI4RwGsUo5BKKaRofA65vl0De7kIIc4AnUYzofspALkmjc/qKJI1vT+BrFNMKzqP4sp2/qbRFneVzFFNabqKYeiNJGoXTVyRJkqSKOVIuSZIkVcxQLkmSJFWsWy/0dM6OJEmSWmW8L1gDujeUs2TJkkqO29/fz9KlSycuqMrZV53F/uoc9lVnsb86h33VnubPn99UOaevSJIkSRUzlEuSJEkVM5RLkiRJFevaOeWSJEmaXDlnBgYGGBoaIoQJr23cbuScqdVqzJgxY6tft6FckiRJk2JgYIC+vj56e7svYg4ODjIwMMDMmTO3qr7TVyRJkjQphoaGujKQA/T29jI0NLTV9Q3lkiRJmhTdNGVlNNvy+g3lkiRJUhMuuOAC1qxZMyX7NpRLkiRJTfj85z9vKJckSZImcumll7Jo0SIWLVrE29/+du6++26OP/54Fi1aRIyRe++9F4DTTz+dq666amO9/fffH4Cf/OQnHHfccZxyyik85znP4W1vexs5Zy688EIeeOABjj/+eI477rhJb3d3zsSXJEnSlBr62gXku/84qfsMT9yb2gmnjLn91ltv5ROf+ATf/OY32XnnnVm2bBmnn346xx9/PDFGvva1r3HmmWdy0UUXjXucW265he9973vstttuvOxlL+MXv/gFf/u3f8v555/PpZdeys477zyprwscKZckSdJ24sc//jEveclLNobmnXbaiRtvvJFXvOIVABx77LH8/Oc/n3A/CxcuZP78+dRqNRYsWMDdd989pe0GR8olSZI0BcYb0W4H9bcwHBoaYv369Ru3TZs2bePznp4eBgcHp7w9jpRLkiRpu3DUUUdx1VVX8cgjjwCwbNkyDj/8cK688koALr/8co488kgA9thjD26++WYArr322hGhfCxz5sxh5cqVU9J2R8olSZK0XTjggAN4xzvewXHHHUetVuOggw7inHPO4Z3vfCef+9zn2HnnnfnYxz4GwIknnsjJJ5/MokWLOProo5k1a9aE+z/xxBM58cQT2XXXXbnssssmte0h5zypO+wQecmSJZUcuL+/n6VLl1ZybG0Z+6qz2F+dw77qLPZX52iHvlq9enVT4XZ7Ndrrnz9/PsCE3yrk9JUWymtWkwem5t6WkiRJ6lyG8hYa+sDpPPa5j1TdDEmSJLUZQ7kkSZJUMUO5JEmSJkWXXqu40ba8fkO5JEmSJkWtVmvJPb3b0eDgILXa1kdrb4koSZKkSTFjxgwGBgZYu3YtIUx4w5HtRs6ZWq3GjBkztnofhnJJkiRNihACM2fOrLoZHcnpK5IkSVLFDOWSJElSxQzlrdblVyVLkiRpc4ZySZIkqWKGckmSJKlihnJJkiSpYoZySZIkqWKGckmSJKlihnJJkiSpYoZySZIkqWKGckmSJKlihvJWCqHqFkiSJKkNGcolSZKkihnKJUmSpIoZyiVJkqSKGcolSZKkihnKJUmSpIoZyiVJkqSKGcolSZKkihnKWy3nqlsgSZKkNmMobym/PEiSJEmbM5RLkiRJFTOUS5IkSRUzlEuSJEkVM5RLkiRJFTOUS5IkSRUzlEuSJEkVM5S3WPY+5ZIkSWpgKG+l4H3KJUmStDlDuSRJklQxQ7kkSZJUMUO5JEmSVDFDuSRJklQxQ7kkSZJUMUO5JEmSVDFDuSRJklQxQ3nL+eVBkiRJGslQ3kp+d5AkSZJGYSiXJEmSKmYolyRJkipmKJckSZIqZiiXJEmSKmYolyRJkipmKJckSZIqZihvNW9TLkmSpAaG8pbyRuWSJEnanKFckiRJqpihXJIkSaqYoVySJEmqWG+rDhRj3Av4LPBMYC1wGXB6SmkwxrgQuBB4CvA74G9TSr8u6wXgX4E3lbv6PPCelFIut49ZV5IkSeoErRwp/yzwIPAEYCHwXOCtMcZpwJXAl4CdgIuBK8v1AKcCLwcOBQ4BXgq8GaCJupIkSVLba2Uo3xtIKaWBlNL9wDXAAuB5FCP2H08prU0pfZLiNiXHlPXeAPxHSumelNK9wH8AJ5XbJqorSZIktb2WTV8BPg6cEGP8AcWo9l8DZ1IE898MT0cp/aZcPxzcb6rbdlO5jibqSpIkSW2vlaH8hxRTUR4DeiimmnwDOANY3lB2OTC3fD6nYftyYE4517xxW2PdjWKMp5bHJ6VEf3//tryWrbK0p4caVHJsbbne3l77qoPYX53Dvuos9lfnsK86W0tCeYyxRjFyfT7wLIowfRHwb8B9wLyGKvOAFeXzlQ3b5wErU0o5xti4rbHuRiml88vjA+SlS5du9evZWhuGhhjKmSqOrS3X399vX3UQ+6tz2Fedxf7qHPZVe5o/f35T5Vo1p3xn4EnAp8u53w8DXwBeDCwGDilHvocdUq6n/Hlo3bZDG7aNV1eSJElqey0ZKU8pLY0x/hE4Lcb47xQj5W+gmP/9A2AD8I4Y4+eAU8pq3yt/XgK8K8b4f4EM/G/gU+W2iepKkiRJba+Vd195JfAi4CHgD8B64J0ppXUUtzx8PfAo8Ebg5eV6gP8EvgXcDNwCXF2uo4m6kiRJUtsLOeeJS21/8pIlS1p+0A3v+zum770/gyef3vJja8s5N6+z2F+dw77qLPZX57Cv2lM5pzxMVK6VI+WSJEmSRmEolyRJkipmKG+17pwuJEmSpHEYyiVJkqSKGcolSZKkihnKJUmSpIoZyiVJkqSKGcolSZKkihnKJUmSpIoZyiVJkqSKGcolSZKkihnKW84vD5IkSdJIhvJWCqHqFkiSJKkNGcolSZKkihnKJUmSpIoZyiVJkqSKGcolSZKkihnKJUmSpIoZyiVJkqSKGcpbzduUS5IkqYGhvJW8T7kkSZJGYSiXJEmSKmYolyRJkipmKJckSZIqZiiXJEmSKmYolyRJkipmKJckSZIqZiiXJEmSKmYob7XstwdJkiRpJEO5JEmSVDFDuSRJklQxQ7kkSZJUMUO5JEmSVDFDuSRJklQxQ7kkSZJUMUO5JEmSVDFDect5n3JJkiSNZChvpRCqboEkSZLakKFckiRJqpihXJIkSaqYoVySJEmqmKFckiRJqpihXJIkSaqYoVySJEmqmKFckiRJqpihvNWyXx4kSZKkkQzlLeWXB0mSJGlzhnJJkiSpYoZySZIkqWKGckmSJKlihnJJkiSpYoZySZIkqWKGckmSJKlihvJW8z7lkiRJamAobyVvUy5JkqRRGMolSZKkihnKJUmSpIoZyiVJkqSKGcolSZKkihnKJUmSpIoZyiVJkqSKGcolSZKkihnKW8yvDpIkSVIjQ3krBb89SJIkSZszlEuSJEkVM5RLkiRJFTOUS5IkSRUzlEuSJEkVM5RLkiRJFTOUS5IkSRUzlLda9k7lkiRJGslQ3lLep1ySJEmbM5RLkiRJFTOUS5IkSRUzlEuSJEkV623lwWKMJwBnAU8C7gdOSin9T4zx+cBnyvXXl+vvKutMB84DjgNWAx9JKX20bp9j1pUkSZI6QctGymOMfwX8G3AyMBd4DnBnjLEfuBw4E9gZuAH4el3Vs4H9gT2Bo4F/jDG+qNznRHUlSZKkttfK6Sv/Arw/pfSzlNJQSunelNK9wCuBxSmlS1NKAxQh/NAY44FlvTcAH0gpLUsp/Q64ADip3DZRXUmSJKnttWT6SoyxBzgc+GaM8Q/ADOAbwD8AC4CbhsumlFbFGO8AFsQYHwCeUL+9fP7y8vmYdYHfN7ThVODUshz9/f2T+hqb8XBvL7UQ2KmCY2vL9fb2VvJ7oq1jf3UO+6qz2F+dw77qbK2aU74r0EcxL/zZwHrgSuAMYA7wUEP55RRTXObULTduY4K6I6SUzgfOLxfz0qVLt+Z1bJMNg4PUhoao4tjacv39/fZVB7G/Ood91Vnsr85hX7Wn+fPnN1WuVdNX1pQ/P5VSui+ltBT4KPBiYCUwr6H8PGBFuY2G7cPbmKBu+wl+eZAkSZI211QojzG+Osb4lPL5ATHGH8YYv9/s3O2U0jLgHqD+O+aHny8GDq071mxgX4q54suA++q3l88XT1S3mXZJkiRJ7aDZ6SvnAM8qn/878HOKUerPAsc0uY8vAG+PMV5DMX3lncBVwBXAuTHGY4GrgfcBv0kpDc8JvwQ4I8Z4A8U0mFMo7uBCE3UlSZKkttfs9JXHpZQeiDHOAP4S+Gfg/cDCLTjWB4BfALcBvwN+BXwwpfQQcCzwQWAZcCRwQl29s4A7gLuA64BzU0rXADRRV5IkSWp7zY6UPxRj3A84GPhFSmltjHEW0PQk6ZTSeuCt5aNx23eBUafCpJTWAm8sH6NtH7OuJEmS1AmaDeUfAG4ENgCvKtctYuStCiVJkiRthaamr6SUvkhxv/A9UkrfKVf/DKeKSJIkSdusqZHyGGMNGKh7DrA0pTQ0VQ2TJEmSukWz01cGGXk7QwBijIPAEuBy4KyU0srGMmq02dsoSZKkLtfs3VfeDnwPeAHwFOCFwP8H/CNwGsXtEj8+FQ2UJEmStnfNjpS/C3haSmn46+5vK+8bfmNKad8Y480UF4JKkiRJ2kLNjpTPA2Y1rJsF7FA+vx+YOVmNkiRJkrpJsyPllwDfiTF+Argb2AP4e+DicvsLgFsnv3mSJEnS9q/ZUP4PwO0Ut0CcD9wHfAa4oNz+feAHk904SZIkqRs0FcrLWx9+rnyMtn1gMhslSZIkdZNmR8qJMb4AWAjMqV+fUnrfZDdKkiRJ6ibNfnnQp4FIMU1ldd0mb7q9pXzHJEmS1KDZkfLXAIemlO6eysZs90KougWSJElqQ83eEnEp8OhUNkSSJEnqVs2OlP8H8OUY44eBB+o3pJTunPRWSZIkSV2k2VB+XvnzJQ3rM9Azec2RJEmSuk+zt0RsdpqLJEmSpC1k2JYkSZIqNuZIeYzxmpTSi8rn/8MYN/NLKT1nitomSZIkdYXxpq9cUvf881PdEEmSJKlbjRnKU0pfqVv8fUrp+sYyMcanT0mrtmfZbw+SJEnSSM3OKf/OGOuvmayGdAW/PEiSJEmjGPfuKzHGGhCAEGMM5fNh+wKDU9g2SZIkqStMdEvEQTZd4NkYwIeAD056iyRJkqQuM1Eo35tidPw6oP4uKxl4KKW0ZqoaJkmSJHWLcUN5Sumu8umeLWiLJEmS1JWa+kZPgBjj/wKeC/RTN7c8pfT6KWiXJEmS1DWauvtKjPEs4D/L8scDDwMvBB6duqZJkiRJ3aHZWyK+EfirlNI7gXXlz5cCe01Vw7Zf3qdckiRJIzUbyndMKd1SPl8XY+xLKf2cYjqLJEmSpG3QbCi/I8a4oHx+C3BajPF1wLKpaZYkSZLUPZq90PMMYJfy+XuArwBzgLdORaMkSZKkbjJhKC+/1XMA+BlAOW1lvylulyRJktQ1Jpy+klIaAq5MKa1rQXskSZKkrtPsnPIfxhifMaUtkSRJkrpUs3PK7wK+HWO8Eribuvv6pZTeNxUNkyRJkrpFs6F8JvCN8vkedeu96bYkSZK0jZoK5Smlk6e6IV0jex4jSZKkkZqdU67JEELVLZAkSVIbMpRLkiRJFTOUS5IkSRUbM5THGM+te35Ma5ojSZIkdZ/xRspPrXv+jTFLSZIkSdom49195aYY42XAb4HpMcb3j1bI+5RLkiRJ22a8UH4cxWj5nkAAnjhKGe/vJ0mSJG2jMUN5SulB4ByAGGOv9yqfJN6nXJIkSQ2a/vKgGONOwEuB3YF7gatSSo9MZeO2O96nXJIkSaNo6paIMcZnAncAbwEOAd4M/KFcL0mSJGkbNDVSDnwceGtK6WvDK2KMrwI+CRwxFQ2TJEmSukWzXx70ZCA1rLsM2G9ymyNJkiR1n2ZD+e3ACQ3rjqeY0iJJkiRpGzQ7feV04KoY4zuAu4C9gP2Bl0xRuyRJkqSu0dRIeUrpJ8C+wKeBG4FPAfuV6yVJkiRtg2ZHykkpLQO+NIVtkSRJkrpSs3PKJUmSJE0RQ7kkSZJUsWa/PMjwLkmSJE2RCcN2jLEHWBVjnN6C9kiSJEldZ8JQnlLaANwG7DL1zZEkSZK6T7N3X/kyxX3KPwHcA+ThDSml701FwyRJkqRu0WwoP638eXbD+gzsM2mtkSRJkrpQU6E8pbT3VDdEkiRJ6lZNf3lQjLEPeAYwP6X09RjjbICU0qqpatx2KeeJy0iSJKmrNHtLxIMpLva8ALiwXP1c4KIpatf2KYSqWyBJkqQ21Oz9x88D3pdSOhBYX667DvjLKWmVJEmS1EWaDeULgC+VzzNsnLYycyoaJUmSJHWTZkP5n4DD6lfEGJ8O/GGyGyRJkiR1m2Yv9DwTuDrG+DlgWozxvcBbgFOmrGWSJElSl2hqpDyldBXwIuBxFHPJ9wRemVK6dgrbJkmSJHWFpm+JmFL6FfDWKWyLJEmS1JWaCuUxxmnAGcCrgfnAEuBrwAdTSgNT1zxJkiRp+9fsSPl5wAHAO4C7KKav/BOwO/DGqWnadsovD5IkSVKDZkP5y4F9U0qPlsu/jTFeT3H3FUN50/zyIEmSJG2u2Vsi3g/Malg3E7hvcpsjSZIkdZ8xR8pjjMfULf4XcE2M8VPAPcATgb8DLtnSA8YY9wduBi5LKb22XPca4MNAP/Ad4I0ppUfKbTsDFwIvAJYC700pfaVuf2PWlSRJkjrBeCPlF9Y93gzMpZhH/lngvcC8cv2W+gzwi+GFGOMC4D+B1wG7AqvLY9SXX1duOxE4r6zTTF1JkiSp7Y05Up5S2nuyDxZjPAF4FPgJsF+5+kTgWymlH5ZlzgR+F2OcCwwBxwIHpZRWAj+KMX6TIoS/Z7y6KaUVk91+SZIkaSo0O6d8m8UY5wHvB97VsGkBcNPwQkrpDoqR8SeXj8GU0m115W8q60xUV5IkSeoIzd6n/FDgY8BCYE65OgA5pTStyWN9ALgwpXRPjLF+/RxgeUPZ5RTTZTYAj42xbaK6ja/hVOBUgJQS/f39TTZ78jzS10cIoZJja8v19vbaVx3E/uoc9lVnsb86h33V2Zq9JeJXgf+muE/5mi09SIxxIbAI+ItRNq+kmJ9ebx6wgmL6yljbJqo7QkrpfOD8cjEvXbq02eZPmg3r19PX00MVx9aW6+/vt686iP3VOeyrzmJ/dQ77qj3Nnz+/qXLNhvLdgPellLb2m2+eB+wF/LkcJZ8D9MQYnwpcAxw6XDDGuA8wHbiNIpT3xhj3TyndXhY5FFhcPl88Tt32423KJUmSNIpmQ/nFwGuAL2/lcc4Hvla3/G6KkH4a8HjgpzHGZwO/pJh3fvnwhZoxxsuB98cY30QxfeZlwLPK/Xx5vLqSJElSJ2g2lP8rRfj9J+CB+g0ppWNGrzKizGqK2xUCEGNcCQyklB4CHooxvoUiYO8CfBc4ua76W4GLgAeBh4HTUkqLy/0unqCuJEmS1PaaDeWXAX8ErmAr5pQ3Simd3bD8FeArY5R9BHj5OPsas64kSZLUCZoN5QuBXVJK66ayMZIkSVI3avY+5f8DPHUqGyJJkiR1q2ZHyv8IXBtjvILN55S/b9JbJUmSJHWRZkP5LOBqYBrwxKlrjiRJktR9mgrlKSXvaCJJkiRNkaZCefmlPKNKKd05ec3ZzgW/PUiSJEmba3aMdX0IAAActElEQVT6yh+AzMjvpBz+ds+eSW2RJEmS1GWanb4y4i4tMcbdgLMo7soiSZIkaRs0e0vEEVJK9wOnAx+e3OZIkiRJ3WerQnnpAIq7skiSJEnaBs1e6Pk/bJpDDkUYXwC8fyoaJUmSJHWTZi/0/HzD8irgppTS7ZPcHkmSJKnrNHuh58VT3ZCukfPEZSRJktRVmp2+Mg04CVgIzKnfllJ6/eQ3a3vlfcolSZK0uWanr1wMHAp8C3hg6pojSZIkdZ9mQ/mLgL1TSo9OZWMkSZKkbtTsLRH/DEyfyoZIkiRJ3arZkfJLgCtjjJ+gYfpKSul7k94qSZIkqYs0G8rfVv78UMP6DOwzec2RJEmSuk+zt0Tce6obIkmSJHWrZueUS5IkSZoihvJWCgHyUNWtkCRJUpsxlLdSCMUsfEmSJKmOobyVHCmXJEnSKAzlrRRqMGQolyRJ0kiG8laqBXJ2/ookSZJGMpS3lNNXJEmStDlDeSt5oackSZJGYShvpVrNkXJJkiRtxlDeSiHAkEPlkiRJGslQ3kreElGSJEmjMJS3Ughkb4koSZKkBobyVgoBvCWiJEmSGhjKWyiEmqFckiRJmzGUt5Ij5ZIkSRqFobyVgrdElCRJ0uYM5a1UC+CFnpIkSWpgKG+pQHb6iiRJkhoYylvJOeWSJEkahaG8lWqGckmSJG3OUN5KXugpSZKkURjKWykEGHKkXJIkSSMZylvJkXJJkiSNwlDeSgGyt0SUJElSA0N5KwXfbkmSJG3OlNhKwS8PkiRJ0uYM5a1Uq3lLREmSJG3GUN5KIXihpyRJkjZjKG8lb4koSZKkURjKWykEsiPlkiRJamAob6UQnFMuSZKkzRjKWyl4oackSZI2ZyhvpRBgaEPVrZAkSVKbMZS3Um8vbNhAdrRckiRJdQzlrdTTW/zcMFhtOyRJktRWDOWt1NNT/NzgFBZJkiRtYihvJUfKJUmSNApDeSv1DodyR8olSZK0iaG8lYanrww6Ui5JkqRNDOWt5PQVSZIkjcJQ3kpe6ClJkqRRGMpbqaev+OlIuSRJkuoYylsoOKdckiRJozCUt1KPd1+RJEnS5gzlrbRxTrkj5ZIkSdrEUN5K3qdckiRJozCUt9LG6Svrq22HJEmS2oqhvJW8JaIkSZJGYShvJb88SJIkSaMwlLfS8Jxyb4koSZKkOobyViqnr2Snr0iSJKmOobyVnL4iSZKkURjKW8kLPSVJkjQKQ3krOadckiRJo+htxUFijNOBzwKLgJ2BO4D3ppS+XW5/PvAZ4EnA9cBJKaW76uqeBxwHrAY+klL6aN2+x6zbdpy+IkmSpFG0aqS8F7gbeC6wA3AGkGKMe8UY+4HLgTMpAvsNwNfr6p4N7A/sCRwN/GOM8UUATdRtL05fkSRJ0ihaMlKeUlpFEa6HXRVj/CNwGLALsDildClAjPFsYGmM8cCU0u+BN1CMfi8DlsUYLwBOAq4BXjlB3fbiSLkkSZJGUcmc8hjjrsCTgcXAAuCm4W1lgL8DWBBj3Al4Qv328vmC8vmYdaey/VtteKTcOeWSJEmq05KR8noxxj7gy8DFKaXfxxjnAA81FFsOzAXm1C03bqPcPlbdxuOeCpwKkFKiv79/W17GVnugVmPW9GnMqej4al5vb29lvyfacvZX57CvOov91Tnsq87W0lAeY6wB/wWsA95Wrl4JzGsoOg9YUW4bXh5o2DZR3RFSSucD55eLeenSpVv3IrZVTy+rV6xgoKrjq2n9/f1U9nuiLWZ/dQ77qrPYX53DvmpP8+fPb6pcy6avxBgDcCGwK3BsSml9uWkxcGhdudnAvhRzxZcB99VvL58vnqjuFL2MbRZ6er3QU5IkSSO0cqT8POApwKKU0pq69VcA58YYjwWuBt4H/KbuQs1LgDNijDdQBPpTgJObrNt+enthw/qJy0mSJKlrtGSkPMa4J/BmYCFwf4xxZfk4MaX0EHAs8EFgGXAkcEJd9bMoLt68C7gOODeldA1AE3XbTuh1pFySJEkjhZxz1W2oQl6yZEk1B37Pm8gHHkztpL+v5PhqnnPzOov91Tnsq85if3UO+6o9lXPKw0TlKrklYjcL06bBeqevSJIkaRNDeYuF6TPI69ZW3QxJkiS1EUN5i4XpM2DtwMQFJUmS1DUM5S0Wps8AR8olSZJUx1DeYsVIuaFckiRJmxjKW82RckmSJDUwlLdYmDET1jmnXJIkSZsYylusNnMWrFkzcUFJkiR1DUN5i4VZs2HtGvKQ3+opSZKkgqG8xcKsOcWTAUfLJUmSVDCUt1htOJSvXlVtQyRJktQ2DOUtFubOK56sWlFtQyRJktQ2DOUtVpu7Q/FkpaFckiRJBUN5i9V22BGAvPKxilsiSZKkdmEob7HavJ2KJyserbYhkiRJahuG8hYLs+dATw88trzqpkiSJKlNGMpbLNRqMHdHePSRqpsiSZKkNmEor8IOO5EfW1Z1KyRJktQmDOVV2GEnWG4olyRJUsFQXoGw8+PgkYeqboYkSZLahKG8Co/bFVavIvsFQpIkScJQXonQv2vxZOkD1TZEkiRJbcFQXgVDuSRJkuoYyqvw+CcAkB9YUnFDJEmS1A4M5RUIM2bBjrvAffdU3RRJkiS1AUN5VeY/ibzkrqpbIUmSpDZgKK9IeOLecO+fyevXV90USZIkVcxQXpU994UNg7Dkz1W3RJIkSRUzlFckPGlfAPKf76i4JZIkSaqaobwqj38CzJgJf76z6pZIkiSpYobyioQQYM/9yHfeWnVTJEmSVDFDeYXCkw+Cu+8kP/Zo1U2RJElShQzlFQoHHw45kxf/quqmSJIkqUKG8irttR/Mngu33lx1SyRJklQhQ3mFQghw4MHk3/yCPDRUdXMkSZJUEUN5xcLCZ8CK5XDH76tuiiRJkipiKK9YWPh06O0l/+qnVTdFkiRJFTGUVyzMmAVPWUi+8SfknKtujiRJkipgKG8D4bCj4JGH4E9/qLopkiRJqoChvA2EQ4+Anl7y9T+ouimSJEmqgKG8DYQ58+CQw8nX/4C8fl3VzZEkSVKLGcrbRO3ov4GVK8g3/LjqpkiSJKnFDOXt4oCDYbfdydde4T3LJUmSuoyhvE2EWo3wkhPgnj+Rf/SdqpsjSZKkFjKUt5FwxLPhyQvIV1xCXrWy6uZIkiSpRQzlbSTUatRe9SZYtYp8xSVVN0eSJEktYihvM+FJ+xKOfjH5h9eS77y16uZIkiSpBQzlbSi8/LWww04MXfgx8prVVTdHkiRJU8xQ3obCzFnU/vadsPR+hr74CXLOVTdJkiRJU8hQ3qbCgYcQXvE6+OVPyddeUXVzJEmSNIUM5W0svPCV8LRnkf/7Yoauv67q5kiSJGmKGMrbWAiB2htPh/2fSr7oY+RfX191kyRJkjQFDOVtLkyfQe3tZ8IT92HovA8z9OPvVt0kSZIkTTJDeQcIM2ZRe9cH4ICDyV/8JENfPo+8fl3VzZIkSdIkMZR3iDBrNrV3vI/w/JeSf/Bths55F/muO6puliRJkiaBobyDhN4+aiecQu1tZ8DqlQx9+N0MXX4xee3aqpsmSZKkbdBbdQO05cKhT6e2zwHkS79A/vZ/k6+/jvCyEwnPPIYQQtXNkyRJ0hZypLxDhbk7UHvj6dTe/SGYPZf8hU8w9OF/IN/+26qbJkmSpC1kKO9w4YCDqP3TfxBe/zZ4+EGGPvIeNpz7T+TFv/KbQCVJkjqE01e2A6G3l/DsF5CPeDb5umvI37mSoY+fBXvsRTjmJYQjnk2YMbPqZkqSJGkMhvLtSJgxk/DCV5CPfjH5Z98nf/db5Es+Tf7q+YS/eAbhyOfCQU8j1HqqbqokSZLqGMq3Q2HadMJzXkR+9gvh1puLgP6r68k//yHM25Fw2FGEw54F+z2V0GNAlyRJqpqhfDsWQoADDyEceAj5xPXwm58z9LPryD/8f+TvXw1z5hIOOhwW/AXhwEMIO+5cdZMlSZK6kqG8S4S+PjjsKHoOO4o8sAYW/5L86+vJN98AP/s+GWD+kwgHHEw44KBiFH2HnaputiRJUlcwlHehMGMmHHYU4bCjyEMb4M93kn/3G/LvbyL/+LvFKDrA43Yj7Lkf7LUfYfe9ip9z5lXadkmSpO2RobzLhVoP7LU/Ya/94a+PJQ8Owl1/IP/ht+Q7byX/8Ta44UdsvLnicFB/0r6EPfct7vAyb8cKX4EkSVLnM5RrhNDbC/seSNj3wI3r8orH4N4/FSH9T7dvHtTn7Qi770nYbQ94wh6Ex8+Hxz8Bdnmcd3qRJElqgqFcEwpz5228YHRYXvlYMe3lnj/BvXeR772L/JPvwdo1m8J6bx88bjfYbXfC43aDXR5f/OzfDfofT+ibVsXLkSRJajuGcm2VMGcePHUh4akLN67LQ0OwfBk8uIT8wBJ4YAn5gXvhvrvJN98Ig+sZ8R2j83aEnfphp10IO+4CO+0CO+5S3AVmh51h7lyYswOh5hfPSpKk7ZuhXJMm1GpFsN5pF8IBB4/YloeG4LFl8NAD5KUPwMMPwMMPkR9ZWoT3W2+BNauKsvUVe3o37XPHXYqwvmPxKML7TjB3B5g1p7gFpCRJUgcylKslQq0GO5Yj4fs/ddQyeWANPPowLF9GXr4MViyHZQ/DIw+RH32Y/Kfb4dFHYP26ovzIAxQj73Pmwpx5hDKoM3cezJ4Ls+cQZs+D2bNhzjyYMaso1+ufgCRJqp6JRG0jzJgJu+0Bu+3BeGPeedXKIpw/Vob3xx6FlcthxWPkFcth1Qryn++E1Stg1UrIRXzPo+2st3dESGfGDMLsuTB9BisetytDG4Zg1myYMato36w5MG06zC5/zpwF06Y7Si9JkraJoVwdJ8yeU4Ti3Z80bniHctrM6pWwelUx8r5mNXnlclizBlatgIHVsHIFec2qIsCveIx8/70wsIbVq1fC0NCmfY3ZoAB90zYF9unTYeZsmDaNMH0mzJhZbp9d/OzrK8J833To7SvC/vTiOX3Tim29vdDTV6zvm2bolyRpO2co13Yt1GrFCPicecVtGmHCID9sl112Yen998Oqx2Dd2iLYD6yBgdXk1atg/foi2K9bC2vXwpqVsH49ec1qWLsGVq0kP3h/Md1m3dri5CBvHu3HDPv1enqLoN7XB9NnQk/PpnUzZ5eBvgz406YX26ZNKz4B6O0tyk+bXtTt7SX09ML0GcVJQG/vpvLTZ0Ctbt8zZnpCIElSC2wXoTzGuDNwIfACYCnw3pTSV6ptlTpdCIHQ11fMhW/cthX7yznDhg0wuA5WrYLB9cVjTRnwBweLW0quXQsbyuU1q4syGwaLumsHYN062DBI3jBYPF+7ptjHY+uLEf/BwaL+wJri+Wht2ZKG9/RCT60M6n1FcO/phVqtCPu9fcWnAbWeYrlcH8oTAGq1Tdt6emFGGfzr1/dNK04KQo3QU7ett684majVNh0vlM8btg1N6yOvXlmW6yv6TpKkDrFdhHLgM8A6YFdgIXB1jPGmlNLiapslbRJCKEJqb28xgj1WuUk8Zh7aUJ4IDBYhfX0R6IvAPxzgN8CG9eSBgU3bN2wonq8dKLdvKNavW1uW2bBp3+vWFicHQ2uL5aHieHntGtgwVCwPlT/XlWXGa/NWvtaHGlcMh/darZhiVKtb7unbeBIwYnv98vBUo8b15XIY/vRh4zHG2Nf06aNvq3/eU4NpMzauCyHUlal7HsoTk+G2D6+rheI5o5TvLU+mhvdFXR3CyGMM1+/p8Yu/JKnFOj6UxxhnA8cCB6WUVgI/ijF+E3gd8J5KGydVLNR6ihHn4bnq45VtQXtyzpCHNg/rA2vKk4GhTeuGhoqTgsH1xfNcbtuwAfJQ8YlC3bY5M2eycsWKYnn9Wlg/CLncT87lfsv9rFtbnJCU23L9/ofLrhsoyg01rC/LbfykItftdyjX/dyw6YRmS9+nKXjvt1hPb/FLEWrlL0fY9Lzx53C4Hz6Z6endtDw8/Wn4+wZC4OG+PjZs2DCy3vBj+JMTQnmMsHk56tePfB6GT0go2zd9+tjlGf7RsO/G8pst17cDCOWJ0sZyox2jblv9fqkrP9q6bdnH8NQ0GuuOthzGLDYwbx55xYrNym3+j0bj+7eF5RoLjtjUZLkx9123KdSKE+r6cmP8Pm2suPG9rSs7vM/RfjcC0Dtt08n48DZG2X9j/Y1tqj8ebNa+jcXLMn6nR8fr+FAOPBkYTCndVrfuJuC5FbVH0hiKEeDyRIG66SWz5275vhqWZ/X3s3rp0m1q32TLDUGenEcJ70Pl9KX1xfOcN63PefN1w59mDOWR24f3Ry5PNMp1awfKMtSVra/LyGOQN32iUV+W3FB3jG1rB0Zsy8PHYNP+e/r6GFy7dtM1FhvbRXFCNbCmblsevRx51DK5vl3DnwrVt2+4PMP7GF7OG1dtKk9dudxQvq7+0IZRrxfpVI2vZHklrdAW6+nlgfpgvqUneRt/hJHbRt1PQ/2xThZrtfKEteGEeqyT1/qT8NHK1j+v9RSfZm6B2gtfSXjKoVtUp5W2h1A+B3isYd1yYMT/8jHGU4FTAVJK9Pf3t6Z1DXp7eys7traMfdVZ7K/O0dvby+AY1zt0ojz8CQ71Jwkbt25aR3nSMOIEgIaTgrpt9eF/xLZct/v6bSP3m4dPyOrLNTRrs4XNymV6envZMDjYUGXzcs3tr/njTliH4fdzjH2Mtb/B9eT16+vW15+E5U0ndsPHy6OVo245k+vLlWXzxhPP+hNCGpaH68NmfbnZCWB9uyhPesuyQ5m8doBaLTA0NLSpjY31R3ufJvzdbNzPKPuuey311Vm/jjw4OOJ92ez9HH4tI/Yx2vve8J6tX7/xe0uaNXvWLKa38f8T20MoXwnMa1g3D1hRvyKldD5wfrmYl1Y0otbf309Vx9aWsa86i/3VOeyrqdLw+dG0mZOy1/7+fh61vzqCf1vjWwGsqOD9mT9/flPltocJSLcBvTHG/evWHQp4kackSZI6QseH8pTSKuBy4P0xxtkxxqOAlwH/VW3LJEmSpOZ0fCgvvRWYCTwIfBU4zdshSpIkqVNsD3PKSSk9Ary86nZIkiRJW2N7GSmXJEmSOpahXJIkSaqYoVySJEmqmKFckiRJqpihXJIkSaqYoVySJEmqmKFckiRJqpihXJIkSaqYoVySJEmqmKFckiRJqpihXJIkSaqYoVySJEmqmKFckiRJqpihXJIkSapYyDlX3YYqdOWLliRJUiXCRAW6daQ8VPWIMd5Y5fF92Ffb68P+6pyHfdVZD/urcx72VVs/JtStoVySJElqG4ZySZIkqWKG8tY7v+oGqGn2VWexvzqHfdVZ7K/OYV91sG690FOSJElqG46US5IkSRUzlEuSJEkV6626Ad0ixrgzcCHwAmAp8N6U0leqbdX2Lcb4A+AZwGC56t6U0gHlttcAHwb6ge8Ab0wpPVJuG7evtqWuCjHGtwEnAQcDX00pnVS37fnAZ4AnAdcDJ6WU7iq3TQfOA44DVgMfSSl9dKrrdrOx+irGuBfwR2BVXfF/Syl9oNxuX7VY+b59FlgE7AzcQfFv0LfL7f5ttZHx+su/r+7kSHnrfAZYB+wKnAicF2NcUG2TusLbUkpzysdwIF8A/CfwOor+WE3xD+OwMftqW+pqhCXAOcBF9StjjP3A5cCZFP9J3QB8va7I2cD+wJ7A0cA/xhhf1IK63WzUvqqzY93f2Afq1p+NfdVqvcDdwHOBHYAzgBRj3Mu/rbY0Zn/VlfHvq4sYylsgxjgbOBY4M6W0MqX0I+CbFMFOrXci8K2U0g9TSisp/vF5ZYxxbhN9tS11VUopXZ5S+gbwcMOmVwKLU0qXppQGKP7zODTGeGC5/Q3AB1JKy1JKvwMuoBjFneq6XWucvpqIfdViKaVVKaWzU0p/SikNpZSuohhtPQz/ttrOBP01EftrO2Qob40nA4Mppdvq1t0EOII69T4cY1waY/xxjPF55boFFO8/ACmlOyhGt5/MxH21LXU1scb3dxXFR7oLYow7AU+o3874fTMpdSflVW3f7oox3hNj/EI5yoZ91R5ijLtS/Lu0GP+22l5Dfw3z76uLGMpbYw7wWMO65cDcCtrSTf4PsA+wO8W9W78VY9yXoj+WN5Qd7o+J+mpb6mpiE72/NGzfkr7Z2roa3VLgCIqPwA+jeK++XG6zryoWY+yj6I+LU0q/x7+ttjZKf/n31YW80LM1VgLzGtbNA1ZU0JaukVK6vm7x4hjjq4EXM35/DI2zjW2sq4mN9/6urFseaNg2lXU1inL61g3l4gPlBaH3xRjnYl9VKsZYA/6L4lO8t5Wr/dtqU6P1l39f3cmR8ta4DeiNMe5ft+5QRn5EpamXgUDxvh86vDLGuA8wnaKfJuqrbamriTW+v7OBfSnmOC4D7qvfzvh9Myl1J+VVdYfhb6Kr2VfViTEGijtA7Qocm1JaX27yb6sNjdNfjfz76gKOlLdASmlVjPFy4P0xxjcBC4GXAc+qtmXbrxjjjsCRwHUUt0R8FfAc4O+BPuCnMcZnA78E3g9cnlJaUdYdr6++vA11VYox9lL8+9MD9MQYZ1D00xXAuTHGY4GrgfcBvyk/zgW4BDgjxngDxX9ipwAnl9umsm7XGqevDgMeBW4HdgI+CfwgpTT80bd9VY3zgKcAi1JKa+rW+7fVnkbtrxjjkfj31XUcKW+dtwIzgQeBrwKnpZQ885w6fRS3cXuIYm7e24GXp5RuK9/3t1AE7Acp5sq9ta7umH21LXU1whnAGuA9wGvL52eklB6iuIPNB4FlFCdWJ9TVO4vioqO7KE64zk0pXQMwxXW72ah9RXG9xjUUH2vfAqwFXl1Xz75qsRjjnsCbKQYE7o8xriwfJ/q31X7G6y/8++pKIec8cSlJ+v/buX8XuaowjONfjY1BSECDZhLYaotICiurYIooaaI28iCR/AUKSlIIohBCioBgYAUDJsU2SfGSYkGbiBgwxaKxiMWCRTSCC2kSjMRfGHbH4l5hbLLsOnqc2e+nmnvnzDnvaS4P79x7JUnSv8ZOuSRJktSYoVySJElqzFAuSZIkNWYolyRJkhozlEuSJEmNGcolSZKkxgzlkqT/XJLvkzzbug5J+r8wlEuSxi7JfJKTreuQpElhKJck/SNJHmpdgyRNOi+kkjTFkgyB2aq63h/PA8tV9XaSx4B5YB+wCiwB+6tqNckAeB94BvgZOF1Vc/0cx4G9wO/AC8BR4NwadRwBTgKPAO+Nd5eSNPnslEvS5nUMWAZ2AI8DbwHDJA8CHwFfA7uAA8AbSQ6O/PZF4CKwHTh/v0WSPAmcAY4AA+BRYPdYdyJJE85OuSRtXveAncBM30m/ApDkaWBHVZ3ox32X5CzwMnCpP7dYVQv959/WWOcl4OOq+ryf/x3gtfFtQ5Imn6Fckjavd4HjwCdJAD6sqlPADDBIcmdk7Bb60N77YR3rDEbHV9UvSW5vtGhJmkaGckmabr8CW0eOn6C7ZYWqukt3C8uxJHuBz5JcpQvQN6pq9j7zDtdRw01gz18HSbbS3cIiSeoZyiVpul0DDidZAp4D9gNfASQ5BHwDfAv8BKzQPfD5JXA3yZvAHPAHXah+uKqubqCGi8AXSfb1c5/AZ5ok6W+8KErSdHsdeB64A7wCLIx8Nwt8Svd2lUXgg6q6XFUrwCHgKeAGcIvu7SrbNlJAVS0BrwIX6LrmP9J36yVJnQeGw/X8AylJkiRp3OyUS5IkSY0ZyiVJkqTGDOWSJElSY4ZySZIkqTFDuSRJktSYoVySJElqzFAuSZIkNWYolyRJkhozlEuSJEmN/QlXUzOYANcppAAAAABJRU5ErkJggg==\n",
      "text/plain": [
       "<Figure size 864x576 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# plot rating frequency of all movies\n",
    "ax = df_users_cnt \\\n",
    "    .sort_values('count', ascending=False) \\\n",
    "    .reset_index(drop=True) \\\n",
    "    .plot(\n",
    "        figsize=(12, 8),\n",
    "        title='Rating Frequency of All Users',\n",
    "        fontsize=12\n",
    "    )\n",
    "ax.set_xlabel(\"user Id\")\n",
    "ax.set_ylabel(\"number of ratings\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1.00    9384.0\n",
       "0.95     403.0\n",
       "0.90     239.0\n",
       "0.85     164.0\n",
       "0.80     121.0\n",
       "0.75      94.0\n",
       "0.70      73.0\n",
       "0.65      58.0\n",
       "0.60      47.0\n",
       "0.55      37.0\n",
       "Name: count, dtype: float64"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df_users_cnt['count'].quantile(np.arange(1, 0.5, -0.05))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can see that the distribution of ratings by users is very similar to the distribution of ratings among movies. They both have long-tail property. Only a very small fraction of users are very actively engaged with rating movies that they watched. Vast majority of users aren't interested in rating movies. So we can limit users to the top 40%, which is about 113,291 users."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "shape of original ratings data:  (27753444, 3)\n",
      "shape of ratings data after dropping both unpopular movies and inactive users:  (24178982, 3)\n"
     ]
    }
   ],
   "source": [
    "# filter data\n",
    "ratings_thres = 50\n",
    "active_users = list(set(df_users_cnt.query('count >= @ratings_thres').index))\n",
    "df_ratings_drop_users = df_ratings_drop_movies[df_ratings_drop_movies.userId.isin(active_users)]\n",
    "print('shape of original ratings data: ', df_ratings.shape)\n",
    "print('shape of ratings data after dropping both unpopular movies and inactive users: ', df_ratings_drop_users.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. Train KNN model for item-based collaborative filtering\n",
    " - Reshaping the Data\n",
    " - Fitting the Model"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 1. Reshaping the Data\n",
    "For K-Nearest Neighbors, we want the data to be in an (artist, user) array, where each row is a movie and each column is a different user. To reshape the dataframe, we'll pivot the dataframe to the wide format with movies as rows and users as columns. Then we'll fill the missing observations with 0s since we're going to be performing linear algebra operations (calculating distances between vectors). Finally, we transform the values of the dataframe into a scipy sparse matrix for more efficient calculations."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [],
   "source": [
    "# pivot and create movie-user matrix\n",
    "movie_user_mat = df_ratings_drop_users.pivot(index='movieId', columns='userId', values='rating').fillna(0)\n",
    "# create mapper from movie title to index\n",
    "movie_to_idx = {\n",
    "    movie: i for i, movie in \n",
    "    enumerate(list(df_movies.set_index('movieId').loc[movie_user_mat.index].title))\n",
    "}\n",
    "# transform matrix to scipy sparse matrix\n",
    "movie_user_mat_sparse = csr_matrix(movie_user_mat.values)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 2. Fitting the Model\n",
    "Time to implement the model. We'll initialize the NearestNeighbors class as model_knn and fit our sparse matrix to the instance. By specifying the metric = cosine, the model will measure similarity bectween artist vectors by using cosine similarity."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "env: JOBLIB_TEMP_FOLDER=/tmp\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "NearestNeighbors(algorithm='brute', leaf_size=30, metric='cosine',\n",
       "         metric_params=None, n_jobs=-1, n_neighbors=20, p=2, radius=1.0)"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "%env JOBLIB_TEMP_FOLDER=/tmp\n",
    "# define model\n",
    "model_knn = NearestNeighbors(metric='cosine', algorithm='brute', n_neighbors=20, n_jobs=-1)\n",
    "# fit\n",
    "model_knn.fit(movie_user_mat_sparse)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4. Use this trained model to make movie recommendations to myself\n",
    "And we're finally ready to make some recommendations!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [],
   "source": [
    "def fuzzy_matching(mapper, fav_movie, verbose=True):\n",
    "    \"\"\"\n",
    "    return the closest match via fuzzy ratio. If no match found, return None\n",
    "    \n",
    "    Parameters\n",
    "    ----------    \n",
    "    mapper: dict, map movie title name to index of the movie in data\n",
    "\n",
    "    fav_movie: str, name of user input movie\n",
    "    \n",
    "    verbose: bool, print log if True\n",
    "\n",
    "    Return\n",
    "    ------\n",
    "    index of the closest match\n",
    "    \"\"\"\n",
    "    match_tuple = []\n",
    "    # get match\n",
    "    for title, idx in mapper.items():\n",
    "        ratio = fuzz.ratio(title.lower(), fav_movie.lower())\n",
    "        if ratio >= 60:\n",
    "            match_tuple.append((title, idx, ratio))\n",
    "    # sort\n",
    "    match_tuple = sorted(match_tuple, key=lambda x: x[2])[::-1]\n",
    "    if not match_tuple:\n",
    "        print('Oops! No match is found')\n",
    "        return\n",
    "    if verbose:\n",
    "        print('Found possible matches in our database: {0}\\n'.format([x[0] for x in match_tuple]))\n",
    "    return match_tuple[0][1]\n",
    "\n",
    "\n",
    "\n",
    "def make_recommendation(model_knn, data, mapper, fav_movie, n_recommendations):\n",
    "    \"\"\"\n",
    "    return top n similar movie recommendations based on user's input movie\n",
    "\n",
    "\n",
    "    Parameters\n",
    "    ----------\n",
    "    model_knn: sklearn model, knn model\n",
    "\n",
    "    data: movie-user matrix\n",
    "\n",
    "    mapper: dict, map movie title name to index of the movie in data\n",
    "\n",
    "    fav_movie: str, name of user input movie\n",
    "\n",
    "    n_recommendations: int, top n recommendations\n",
    "\n",
    "    Return\n",
    "    ------\n",
    "    list of top n similar movie recommendations\n",
    "    \"\"\"\n",
    "    # fit\n",
    "    model_knn.fit(data)\n",
    "    # get input movie index\n",
    "    print('You have input movie:', fav_movie)\n",
    "    idx = fuzzy_matching(mapper, fav_movie, verbose=True)\n",
    "    # inference\n",
    "    print('Recommendation system start to make inference')\n",
    "    print('......\\n')\n",
    "    distances, indices = model_knn.kneighbors(data[idx], n_neighbors=n_recommendations+1)\n",
    "    # get list of raw idx of recommendations\n",
    "    raw_recommends = \\\n",
    "        sorted(list(zip(indices.squeeze().tolist(), distances.squeeze().tolist())), key=lambda x: x[1])[:0:-1]\n",
    "    # get reverse mapper\n",
    "    reverse_mapper = {v: k for k, v in mapper.items()}\n",
    "    # print recommendations\n",
    "    print('Recommendations for {}:'.format(fav_movie))\n",
    "    for i, (idx, dist) in enumerate(raw_recommends):\n",
    "        print('{0}: {1}, with distance of {2}'.format(i+1, reverse_mapper[idx], dist))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "You have input movie: Iron Man\n",
      "Found possible matches in our database: ['Iron Man (2008)', 'Iron Man 3 (2013)', 'Iron Man 2 (2010)']\n",
      "\n",
      "Recommendation system start to make inference\n",
      "......\n",
      "\n",
      "Recommendations for Iron Man:\n",
      "1: Bourne Ultimatum, The (2007), with distance of 0.4217848777770996\n",
      "2: Sherlock Holmes (2009), with distance of 0.4190899133682251\n",
      "3: Inception (2010), with distance of 0.39293038845062256\n",
      "4: Avatar (2009), with distance of 0.38322633504867554\n",
      "5: WALL·E (2008), with distance of 0.38314002752304077\n",
      "6: Star Trek (2009), with distance of 0.37503182888031006\n",
      "7: Batman Begins (2005), with distance of 0.3701704144477844\n",
      "8: Iron Man 2 (2010), with distance of 0.37011009454727173\n",
      "9: Avengers, The (2012), with distance of 0.3579972982406616\n",
      "10: Dark Knight, The (2008), with distance of 0.3010351061820984\n"
     ]
    }
   ],
   "source": [
    "my_favorite = 'Iron Man'\n",
    "\n",
    "make_recommendation(\n",
    "    model_knn=model_knn,\n",
    "    data=movie_user_mat_sparse,\n",
    "    fav_movie=my_favorite,\n",
    "    mapper=movie_to_idx,\n",
    "    n_recommendations=10)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This is very interesting that my **KNN** model recommends movies that were also produced in very similar years. However, the cosine distance of all those recommendations are actually quite small. This is probabily because there is too many zero values in our movie-user matrix. With too many zero values in our data, the data sparsity becomes a real issue for **KNN** model and the distance in **KNN** model starts to fall apart. So I'd like to dig deeper and look closer inside our data."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### (extra inspection) \n",
    "Let's now look at how sparse the movie-user matrix is by calculating percentage of zero values in the data."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "There is about 98.35% of ratings in our data is missing\n"
     ]
    }
   ],
   "source": [
    "# calcuate total number of entries in the movie-user matrix\n",
    "num_entries = movie_user_mat.shape[0] * movie_user_mat.shape[1]\n",
    "# calculate total number of entries with zero values\n",
    "num_zeros = (movie_user_mat==0).sum(axis=1).sum()\n",
    "# calculate ratio of number of zeros to number of entries\n",
    "ratio_zeros = num_zeros / num_entries\n",
    "print('There is about {:.2%} of ratings in our data is missing'.format(ratio_zeros))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This result confirms my hypothesis. The vast majority of entries in our data is zero. This explains why the distance between similar items or opposite items are both pretty large."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5. Deep dive into the bottleneck of item-based collaborative filtering.\n",
    " - cold start problem\n",
    " - data sparsity problem\n",
    " - popular bias (how to recommend products from the tail of product distribution)\n",
    " - scalability bottleneck"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We saw there is 98.35% of user-movie interactions are not yet recorded, even after I filtered out less-known movies and inactive users. Apparently, we don't even have sufficient information for the system to make reliable inferences for users or items. This is called **Cold Start** problem in recommender system.\n",
    "\n",
    "There are three cases of cold start:\n",
    "\n",
    "1. New community: refers to the start-up of the recommender, when, although a catalogue of items might exist, almost no users are present and the lack of user interaction makes very hard to provide reliable recommendations\n",
    "2. New item: a new item is added to the system, it might have some content information but no interactions are present\n",
    "3. New user: a new user registers and has not provided any interaction yet, therefore it is not possible to provide personalized recommendations\n",
    "\n",
    "We are not concerned with the last one because we can use item-based filtering to make recommendations for new user. In our case, we are more concerned with the first two cases, especially the second case.\n",
    "\n",
    "The item cold-start problem refers to when items added to the catalogue have either none or very little interactions. This constitutes a problem mainly for collaborative filtering algorithms due to the fact that they rely on the item's interactions to make recommendations. If no interactions are available then a pure collaborative algorithm cannot recommend the item. In case only a few interactions are available, although a collaborative algorithm will be able to recommend it, the quality of those recommendations will be poor. This arises another issue, which is not anymore related to new items, but rather to unpopular items. In some cases (e.g. movie recommendations) it might happen that a handful of items receive an extremely high number of iteractions, while most of the items only receive a fraction of them. This is also referred to as popularity bias. Please recall previous long-tail skewed distribution of movie rating frequency plot.\n",
    "\n",
    "In addtition to that, scalability is also a big issue in KNN model too. Its time complexity is O(nd + kn), where n is the cardinality of the training set and d the dimension of each sample. And KNN takes more time in making inference than training, which increase the prediction latency"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6. Further study\n",
    "\n",
    "Use spark's ALS to solve above problems"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
